Polymorphism

Hi again. I am once again having a hard time understanding the concepts in this C++ book. There's this program in the book that's supposed to demonstrate polymorphism. I pasted the complete code in at the bottom of this message. The book says that using object pointers like this is useful because "it allows you to deal with objects without requiring that you know their exact type." But I still wasn't totally clear on why you would use this, so I tried changing the code to help me understand what was happening. So I changed main() to this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int main()
{
    char sel;
    Enemy* pBadGuy = new Boss();
	cout << "Select (e)nemy or (b)oss.";
	do 
	{
		cout << "\n(enter 'e' or 'b')";
		cin >> sel;
	} while ((sel != 'e') && (sel != 'b'));
	Enemy en1(9);
	Boss b1(2);
	if (sel == 'e')
	{
		*pBadGuy = en1;
	}
	else 
	{
		*pBadGuy = b1;
	}
    pBadGuy->Attack();
	
    cout << "\n\nDeleting pointer to Enemy:\n";
    delete pBadGuy;
    pBadGuy = 0;
   
    return 0;
}


The first thing I noticed was that my changes caused a runtime error to occur at the end, when pBadGuy is deleted. But also I noticed that the user's input does change the numbers that appear in the Attack() message, but it doesn't change the rest of it; you always get the "Boss" message. So I think maybe I misunderstood what the book meant about how you can deal with an object without knowing its exact type. Perhaps someone can tell me where I went wrong?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//Polymorphic Bad Guy
//Demonstrates calling member functions dynamically

#include <iostream>

using namespace std;

class Enemy
{
public:
    Enemy(int damage = 10);
    virtual ~Enemy();
    void virtual Attack() const;
    
protected:
    int* m_pDamage;
};

Enemy::Enemy(int damage)
{
    m_pDamage = new int(damage);
}

Enemy::~Enemy()               
{
    cout << "In Enemy destructor, deleting m_pDamage.\n";
    delete m_pDamage;
    m_pDamage = 0;
}

void Enemy::Attack() const
{
    cout << "An enemy attacks and inflicts " << *m_pDamage << " damage points.";
}  

class Boss : public Enemy
{
public:
    Boss(int multiplier = 3); 
    virtual ~Boss();
    void virtual Attack() const;
    
protected:
    int* m_pMultiplier; 
};

Boss::Boss(int multiplier)
{
    m_pMultiplier = new int(multiplier);
}

Boss::~Boss()                 
{
    cout << "In Boss destructor, deleting m_pMultiplier.\n";
    delete m_pMultiplier;
    m_pMultiplier = 0;
} 

void Boss::Attack() const
{
    cout << "A boss attacks and inflicts " << (*m_pDamage) * (*m_pMultiplier)
         << " damage points.";
} 

int main()
{
    cout << "Calling Attack() on Boss object through pointer to Enemy:\n";
    Enemy* pBadGuy = new Boss();
    pBadGuy->Attack();
   
    cout << "\n\nDeleting pointer to Enemy:\n";
    delete pBadGuy;
    pBadGuy = 0;
   
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Enemy* pBadGuy; // pBadGuy is a pointer
// a block of memory big enough to hold a Boss object
// is reserved and the Boss object is initialized.
// The address of the block is X
pBadGuy = new Boss(); // The value of pBadGuy is now X

// a block of memory big enough to hold an Enemy object
// is reserved and the Enemy object is initialized.
// The address of the block is Y
Enemy en1(9);

*pBadGuy = en1;  // copy assignment from Enemy in Y into Enemy in X
// Only the Enemy portion of the block X is adjusted
// The whole block X now contains bits from original Boss and
// value copy of Enemy en1
	
delete pBadGuy; // An attempt to release block X, which should
// call destructor of Boss, but since the block is inconsistent ... 

Try this:
1
2
3
4
5
Enemy* pBadGuy = 0; // pBadGuy is a pointer
Enemy en1(9); // en1 is an Enemy
pBadGuy = &en1; // pBadGuy points to en1
pBadGuy->Attack();
// no delete 
Here's about the simplest way I can think of for explaining polymorphism. That said, it doesn't really show off all of the benefits.

Look at the example below. We have an abstract base struct, Animal, from which our Cat and Dog structs are derived. We have a virtual Noise method, of which Cats and Dogs have their own implementations. Though it doesn't make much difference in this example, the base class destructor is virtual, for brevity.

In our main code, we have a vector of animals. We add five animals to this vector, which are randomly cats or dogs based on whether a random number we generate is zero or one. Note that we're pushing back new Cat and Dog objects to a vector of Animals. The vector doesn't care that they're Cats or Dogs - it's happy accepting that everything here is an animal.

We then loop through our vector calling the Noise method. Note, again, that the we're not bothered about what animal is involved here. We're simply calling the Noise method for the animal at each vector location. Polymorphism allows the cats and dogs to call their respective implementations of Noise.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <iostream>
#include <ctime>
#include <vector>
#include <cstdlib>

struct Animal
{
    virtual ~Animal(){};
    virtual void Noise() = 0;
};

struct Cat: public Animal
{
    void Noise()
    {
        std::cout << "Meow\n";
    }
};

struct Dog: public Animal
{
    void Noise()
    {
        std::cout << "Woof\n";
    }
};

int main( int argc, char* argv[] )
{
    srand( time( NULL ) );

    std::vector<Animal*> animals;

    while( animals.size() < 5 )
        rand() % 2 ? animals.push_back( new Cat() ) : animals.push_back( new Dog() );
    
    for( auto i : animals )
        i->Noise();

    // Clean up
    for( auto &i : animals )
        delete i;

    animals.clear();

    return 0;
}


A good way to think about how this helps is to thing how cumbersome it could become without polymorphism.

Hope this helps. :-)
Last edited on
Thanks keskiverto and iHutch. Your examples were actually much more helpful than the ones in the book.

But I'm not familiar with some of the commands in your code, iHutch. Is "struct" the same as "class"? And it looks like there are a lot of abbreviated usages that I didn't know about. I've never seen "for" or "while" loops without brackets!

Also, my IDE doesn't seem to like the use of "auto" -- I got compile errors on those lines. How does "auto" work?

Well anyway, even though I couldn't get it to run, it definitely helped clarify things for me.
Is "struct" the same as "class"?


Yes. The only difference is that structs have a default public privacy specifier and classes have a default private privacy specifier.

I've never seen "for" or "while" loops without brackets!


Personally I always put them there, but yes, you do not need them; they always run on the following line.

Also, my IDE doesn't seem to like the use of "auto" -- I got compile errors on those lines. How does "auto" work?


auto is a keyword redefined in C++11 to perform automatic type deduction.
http://www.stroustrup.com/C++11FAQ.html#auto

Your compiler is probably older and may not support those features.
Last edited on
Yeah, I'm using an old IDE -- Xcode v3. I can't update my Mac to Mountain Lion right now because I have a lot of old software that I still use that wouldn't work in Mountain Lion.

Anyway, is there any way to make this work without using "auto"? I tried this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
#include <ctime>
#include <vector>
#include <cstdlib>

struct Animal
{
    virtual ~Animal(){};
    virtual void Noise() = 0;
};

struct Cat: public Animal
{
    void Noise()
    {
        std::cout << "Meow\n";
    }
};

struct Dog: public Animal
{
    void Noise()
    {
        std::cout << "Woof\n";
    }
};

int main( int argc, char* argv[] )
{
    srand( time( NULL ) );
	
    std::vector<Animal*> animals;
    std::vector<Animal*>::iterator it;
	
    while( animals.size() < 5 )
        rand() % 2 ? animals.push_back( new Cat() ) : animals.push_back( new Dog() );
    
    for( it = animals.begin(); it != animals.end(); it++ )
	{
        it->Noise();
	}
	
    // Clean up
    for( it = animals.begin(); it != animals.end(); it++ )
	{
        delete *it;
	}
	
    animals.clear();
	
    return 0;
}


but I got a compile error at it->Noise();
What is the error message?
The reason the iterator doesn't work with that syntax is because the vector is a vector of pointers. We need to dereference the iterator itself.

1
2
for( it = animals.begin(); it != animals.end(); ++it )
    (*it)->Noise();


If you download the command line tools in Xcode (Downloads panel -> Components tab) then it should download gcc, the compiler I used to compile the original code.

I think the version of gcc might differ depending on which version of Xcode you use, but don't quote me on it. Are you running Lion?

Worth a shot, anyway. The support of C++11 features is staggered throughout the gcc releases ( http://gcc.gnu.org/projects/cxx0x.html ). Chances are that whatever version is downloaded, some C++11 features should work.

Edit: Fixed broken link
Last edited on
Yep, that fixed it, thanks. This program is going up on my office wall, along with other printouts of helpful code examples.

Hmm... I don't even see a downloads panel. Xcode 3 has a pretty different interface from 4 and 5. I'm actually running on Snow Leopard, sadly. And I'm probably going to have to stay on Snow Leopard until I can afford a new computer. (I'm actually learning C++ partly because I thought it might help with my job search.)

So do you have to use pointers for polymorphism to work? Like, would this not work?
std::vector<Animal> animals;
Ah, I see. I've never used Xcode 3.

I did find a gcc pre-built installation package for Snow Leopard. However, it only installs gcc 4.2, which doesn't have any C++11 support. You could probably look at building your own installation or checking out clang (another compiler with C++11 support) but I feel it's probably not worth the trouble at the moment.

In short, yes, you need pointers for polymorphism to work. If you look at the code example above, the animals vector doesn't know what type of animals (cats or dogs, that is) will be in the vector until runtime. The code is valid because C++ understands that pointers to base and derived classes are type compatible.

Through our use of virtual functions and polymorphism, we employ dynamic binding, whereby the correct virtual method is looked up at runtime. In essence, when we create a cat or dog object a hidden pointer to the vtable, the virtual method table used to look up the correct method for each object, is added as a member of the class. So, even though our vector contains pointers to animals, at runtime it knows which of the respective Noise() methods to run by looking them up in the vtable.

Since stack objects are typed at compile time, we can't achieve this without the use of pointers.

It sounds a bit messy and, in truth, it is. C++ isn't the ideal language for polymorphism, largely because of its roots in C. We tend to have to make a bit more of an effort in C++ (virtual functions, dynamic binding and the like) whereas it'll work a little easier in other languages, such as Java.

There's a pretty good polymorphism explanation right here on this site: http://www.cplusplus.com/doc/tutorial/polymorphism/

Good luck with the job search. :-)
Okay, I get it. Well, thanks again, this has been very educational.
Topic archived. No new replies allowed.