The previous hour emphasized the fact that a Dog
object is a Mammal
object. This meant that the Dog
object inherited the attributes (data) and capabilities (member functions) of its base class. The relationship between a base class and derived class runs deeper than that in C++.
Polymorphism allows derived objects to be treated as if they were base objects. For example, suppose you create specialized Mammal
types such as Dog
, Cat
, Horse
, and so forth. All these derive from Mammal
, and Mammal
has a number of member functions factored out of the derived classes. One such function is speak()
, which implements the capability of all mammals to make noise.
You’d like to teach each of the derived types to specialize how they speak. A dog says “woof,” a cat says “meow,” and so forth. Each class must be able to override how it implements the speak()
method.
At the same time, when you have a collection of Mammal
objects such as a Farm
with Dog
, Cat
, Horse
, and Cow
objects, you want the farm to be able to tell each of these objects to speak()
without knowing or caring about the details of how they implement the speak()
method. When you treat these objects as if they are all mammals by calling the Mammal.speak()
method, you are treating them polymorphically.
By the Way
Polymorphism is an unusual word that means the ability to take many forms. It comes from the roots poly, which means many, and morph, which means form. You are dealing with Mammal in its many forms.
You can use polymorphism to declare a pointer to Mammal
and assign to it the address of a Dog
object you create on the heap. Because a Dog
“is a” Mammal
, the following is perfectly legal:
Mammal* pMammal = new Dog;
You then can use this pointer to invoke any member function on Mammal
. What you would like is for those functions that are overridden in Dog
to call the correct function.
Virtual member functions let you do that. When you treat these objects polymorphically, you call the method on the Mammal
pointer and you don’t know or care what the actual object is or how it implements its method.
The Mammal8 program in Listing 17.1 illustrates how virtual functions implement polymorphism.
1: #include <iostream>
2:
3: class Mammal
4: {
5: public:
6: Mammal():age(1) { std::cout << "Mammal constructor ...
"; }
7: ~Mammal() { std::cout << "Mammal destructor ...
"; }
8: void move() const { std::cout << "Mammal, move one step
"; }
9: virtual void speak() const { std::cout << "Mammal speak!
"; }
10:
11: protected:
12: int age;
13: };
14:
15: class Dog : public Mammal
16: {
17: public:
18: Dog() { std::cout << "Dog constructor ...
"; }
19: ~Dog() { std::cout << "Dog destructor ..
"; }
20: void wagTail() { std::cout << "Wagging tail ...
"; }
21: void speak() const { std::cout << "Woof!
"; }
22: void move() const { std::cout << "Dog moves 5 steps ...
"; }
23: };
24:
25: int main()
26: {
27: Mammal *pDog = new Dog;
28: pDog->move();
29: pDog->speak();
30: return 0;
31: }
Mammal8 displays this output:
Mammal constructor ...
Dog constructor ...
Mammal, move one step
Woof!
On line 9, Mammal
is provided a virtual method called speak()
. The designer of the class thereby signals that she expects this class to eventually be another class’s base type. The derived class will probably want to override this function.
On line 27, a pointer to Mammal
is created, pDog
, but it is assigned the address of a new Dog
object. Because a Dog
is a Mammal
, this is a legal assignment. The pointer then is used to call the move()
function. Because the compiler knows pDog
only to be a Mammal
, it looks to the Mammal
object to find the move()
function.
On line 29, the pointer then calls the speak()
function. Because speak()
is virtual, the speak()
function overridden in Dog
is invoked.
As far as the calling function knew, it had a Mammal
pointer, but here a function of Dog
was called. In fact, if you have an array of pointers to Mammal
, each of which points to a subclass of Mammal
, you can call each in turn and the correct function is called. The Mammal9 program (Listing 17.2) illustrates this idea.
1: #include <iostream>
2:
3: class Mammal
4: {
5: public:
6: Mammal():age(1) { }
7: ~Mammal() { }
8: virtual void speak() const { std::cout << "Mammal speak!
"; }
9: protected:
10: int age;
11: };
12:
13: class Dog : public Mammal
14: {
15: public:
16: void speak() const { std::cout << "Woof!
"; }
17: };
18:
19: class Cat : public Mammal
20: {
21: public:
22: void speak() const { std::cout << "Meow!
"; }
23: };
24:
25: class Horse : public Mammal
26: {
27: public:
28: void speak() const { std::cout << "Whinny!
"; }
29: };
30:
31: class Pig : public Mammal
32: {
33: public:
34: void speak() const { std::cout << "Oink!
"; }
35: };
36:
37: int main()
38: {
39: Mammal* array[5];
40: Mammal* ptr;
41: int choice, i;
42: for (i = 0; i < 5; i++)
43: {
44: std::cout << "(1) dog (2) cat (3) horse (4) pig: ";
45: std::cin >> choice;
46: switch (choice)
47: {
48: case 1:
49: ptr = new Dog;
50: break;
51: case 2:
52: ptr = new Cat;
53: break;
54: case 3:
55: ptr = new Horse;
56: break;
57: case 4:
58: ptr = new Pig;
59: break;
60: default:
61: ptr = new Mammal;
62: break;
63: }
64: array[i] = ptr;
65: }
66: for (i=0; i < 5; i++)
67: {
68: array[i]->speak();
69: }
70: return 0;
71: }
Here’s sample output for Mammal9:
(1) dog (2) cat (3) horse (4) pig: 1
(1) dog (2) cat (3) horse (4) pig: 2
(1) dog (2) cat (3) horse (4) pig: 3
(1) dog (2) cat (3) horse (4) pig: 4
(1) dog (2) cat (3) horse (4) pig: 5
Woof!
Meow!
Whinny!
Oink!
Mammal speak!
This stripped-down program, which provides only the barest functionality to each class, illustrates virtual member functions in their purest form. Four classes are declared (Dog
, Cat
, Horse
, and Pig
), all derived from Mammal
.
On line 8, Mammal
’s speak()
function is declared to be virtual. On lines 16, 22, 28, and 34, the four derived classes override the implementation of speak()
.
The user is prompted to pick which objects to create, and the pointers are added to the array in lines 42–65.
By the Way
Note that at compile time it is impossible to know which objects will be created, and therefore, which speak()
methods will be invoked. The pointer ptr
is bound to its object at runtime. This is called late binding, or sometimes runtime binding, as opposed to static binding, or compile-time binding.
When a derived object, such as a Dog
object, is created, first the constructor for the base class is called, and then the constructor for the derived class is called. Figure 17.1 shows what the Dog
object looks like after it is created. Note that the Mammal
part of the object is contiguous in memory with the Dog
part.
When a virtual function is created in an object, the object must keep track of that function. Many compilers build a virtual function table, called a v-table. One of these is kept for each type, and each object of that type keeps a virtual table pointer (called a vptr or v-pointer), which points to that table.
Although implementations vary, all compilers must accomplish the same thing, so you won’t be too wrong with this description.
Each object’s vptr points to the v-table that, in turn, has a pointer to each of the virtual member functions. When the Mammal
part of the Dog
is created, the vptr is initialized to point to the virtual methods for Mammal
, as shown in Figure 17.2.
When the Dog
constructor is called and the Dog
part of this object is added, the vptr
is adjusted to point to the virtual function overrides (if any) in the Dog
object, as illustrated in Figure 17.3.
When a pointer to a Mammal
is used, the vptr
continues to point to the correct function, depending on the real type of the object. Thus, when speak()
is invoked, the correct function is invoked.
If the Dog
object had a member function called wagTail()
that was not in the Mammal
class, you could not use the pointer to Mammal
to access that function (unless you cast it to be a pointer to Dog
). Because wagTail()
is not a virtual function and is not in a Mammal
object, you can’t get there without either a Dog
object or a Dog
pointer.
Although you can transform the Mammal
pointer into a Dog
pointer, there usually are better and safer ways to call the wagTail()
method. C++ frowns on explicit casts because they are error-prone. This subject is addressed in depth when multiple-inheritance is covered in Hour 18, “Making Use of Advanced Polymorphism,” and again when templates are covered in Hour 24, “Dealing with Exceptions and Error Handling.”
Note that the virtual function magic only operates on pointers and references. Passing an object by value will not enable the virtual member functions to be invoked. The Mammal10 program in Listing 17.3 illustrates this problem.
1: #include <iostream>
2:
3: class Mammal
4: {
5: public:
6: Mammal():age(1) { }
7: ~Mammal() { }
8: virtual void speak() const { std::cout << "Mammal speak!
"; }
9: protected:
10: int age;
11: };
12:
13: class Dog : public Mammal
14: {
15: public:
16: void speak() const { std::cout << "Woof!
"; }
17: };
18:
19: class Cat : public Mammal
20: {
21: public:
22: void speak()const { std::cout << "Meow!
"; }
23: };
24:
25: void valueFunction(Mammal);
26: void ptrFunction(Mammal*);
27: void refFunction(Mammal&);
28:
29: int main()
30: {
31: Mammal* ptr=0;
32: int choice;
33: while (1)
34: {
35: bool fQuit = false;
36: std::cout << "(1) dog (2) cat (0) quit: ";
37: std::cin >> choice;
38: switch (choice)
39: {
40: case 0:
41: fQuit = true;
42: break;
43: case 1:
44: ptr = new Dog;
45: break;
46: case 2:
47: ptr = new Cat;
48: break;
49: default:
50: ptr = new Mammal;
51: break;
52: }
53: if (fQuit)
54: {
55: break;
56: }
57: ptrFunction(ptr);
58: refFunction(*ptr);
59: valueFunction(*ptr);
60: }
61: return 0;
62: }
63:
64: void valueFunction(Mammal mammalValue) // This function is called last
65: {
66: mammalValue.speak();
67: }
68:
69: void ptrFunction (Mammal *pMammal)
70: {
71: pMammal->speak();
72: }
73:
74: void refFunction (Mammal &rMammal)
75: {
76: rMammal.speak();
77: }
Here’s a sample run and the corresponding output:
(1) dog (2) cat (0) quit: 1
Woof!
Woof!
Mammal speak!
(1) dog (2) cat (0) quit: 2
Meow!
Meow!
Mammal speak!
(1)dog (2)cat (0)Quit: 0
On lines 3–23, stripped-down versions of the Mammal
, Dog
, and Cat
classes are declared. Three functions are declared: ptrFunction()
, refFunction()
, and valueFunction()
. They take a pointer to a Mammal
, a Mammal
reference, and a Mammal
object, respectively. All three functions then do the same thing; they call the speak()
method.
The user is prompted to choose a Dog
or Cat
; based on the choice she makes, a pointer to the correct type is created on lines 38–52.
In the first line of the output, the user chooses Dog
. The Dog
object is created on the heap in line 44. The Dog
then is passed as a pointer, as a reference, and by value to the three functions. The pointer and references all invoke the virtual member functions, and the Dog->speak()
member function is invoked. This is shown on the first two lines of output after the user’s choice.
The dereferenced pointer is passed by value, however. The function expects a Mammal
object, so the compiler slices down the Dog
object to just the Mammal
part. At that point, the Mammal speak()
method is called, as reflected in the third line of output after the user’s choice.
This experiment then is repeated for the Cat
object, with similar results.
It is legal and common to pass a pointer to a derived object when a pointer to a base object is expected. What happens when that pointer to a derived subject is deleted? If the destructor is virtual, as it should be, the right thing happens—the derived class’s destructor is called. Because the derived class’s destructor will automatically invoke the base class’s destructor, the entire object will be properly destroyed.
The rule of thumb is this: If any of the functions in your class are virtual, the destructor should also be virtual.
As previously stated, no constructor can be virtual. Nonetheless, there are times when your program desperately needs to be able to pass in a pointer to a base object and have a copy of the correct derived object that is created. A common solution to this problem is to create a clone member function in the base class and to make it virtual. A clone function creates a new copy of the current object and returns that object.
Because each derived class overrides the clone function, a copy of the derived class is created. The Mammal11 program (Listing 17.4) illustrates how this is used.
1: #include <iostream>
2:
3: class Mammal
4: {
5: public:
6: Mammal():age(1) { std::cout << "Mammal constructor ...
"; }
7: virtual ~Mammal() { std::cout << "Mammal destructor ...
"; }
8: Mammal (const Mammal &rhs);
9: virtual void speak() const { std::cout << "Mammal speak!
"; }
10: virtual Mammal* clone() { return new Mammal(*this); }
11: int getAge() const { return age; }
12:
13: protected:
14: int age;
15: };
16:
17: Mammal::Mammal (const Mammal &rhs):age(rhs.getAge())
18: {
19: std::cout << "Mammal copy constructor ...
";
20: }
21:
22: class Dog : public Mammal
23: {
24: public:
25: Dog() { std::cout << "Dog constructor ...
"; }
26: virtual ~Dog() { std::cout << "Dog destructor ...
"; }
27: Dog (const Dog &rhs);
28: void speak() const { std::cout << "Woof!
"; }
29: virtual Mammal* clone() { return new Dog(*this); }
30: };
31:
32: Dog::Dog(const Dog &rhs):
33: Mammal(rhs)
34: {
35: std::cout << "Dog copy constructor ...
";
36: }
37:
38: class Cat : public Mammal
39: {
40: public:
41: Cat() { std::cout << "Cat constructor ...
"; }
42: virtual ~Cat() { std::cout << "Cat destructor ...
"; }
43: Cat (const Cat&);
44: void speak() const { std::cout << "Meow!
"; }
45: virtual Mammal* Clone() { return new Cat(*this); }
46: };
47:
48: Cat::Cat(const Cat &rhs):
49: Mammal(rhs)
50: {
51: std::cout << "Cat copy constructor ...
";
52: }
53:
54: enum ANIMALS { MAMMAL, DOG, CAT};
55: const int numAnimalTypes = 3;
56: int main()
57: {
58: Mammal *array[numAnimalTypes];
59: Mammal *ptr;
60: int choice, i;
61: for (i = 0; i < numAnimalTypes; i++)
62: {
63: std::cout << "(1) dog (2) cat (3) mammal: ";
64: std::cin >> choice;
65: switch (choice)
66: {
67: case DOG:
68: ptr = new Dog;
69: break;
70: case CAT:
71: ptr = new Cat;
72: break;
73: default:
74: ptr = new Mammal;
75: break;
76: }
77: array[i] = ptr;
78: }
79: Mammal *otherArray[numAnimalTypes];
80: for (i=0; i < numAnimalTypes; i++)
81: {
82: array[i]->speak();
83: otherArray[i] = array[i]->clone();
84: }
85: for (i=0; i < numAnimalTypes; i++)
86: {
87: otherArray[i]->speak();
88: }
89: return 0;
90: }
The following output demonstrates one run of the program:
1: (1) dog (2) cat (3) mammal: 1
2: Mammal constructor...
3: Dog constructor...
4: (1) dog (2) cat (3) mammal: 2
5: Mammal constructor...
6: Cat constructor...
7: (1) dog (2) cat (3) mammal: 3
8: Mammal constructor...
9: Woof!
10: Mammal copy constructor...
11: Dog copy constructor...
12: Meow!
13: Mammal copy constructor...
14: Cat copy constructor...
15: Mammal speak!
16: Mammal copy constructor...
17: Woof!
18: Meow!
19: Mammal speak!
Listing 17.4 is similar to the previous two listings, except that a new virtual function has been added to the Mammal
class: clone()
. This function returns a pointer to a new Mammal
object by calling the copy constructor, passing in itself (*this
) as a const
reference.
Dog
and Cat
both override the clone()
function, initializing their data and passing in copies of themselves to their own copy constructors. Because clone()
is virtual, this effectively creates a virtual copy constructor, as shown on line 83.
The user is prompted to choose dogs, cats, or mammals, and these are created on lines 65–76. A pointer to each choice is stored in an array on line 77.
As the program iterates over the array, each object has its speak()
and its clone()
method called, in turn, on lines 82 and 83. The result of the clone()
call is a pointer to a copy of the object, which then is stored in a second array on line 83.
On line 1 of the output, the user is prompted and responds with 1, choosing to create a dog. The Mammal
and Dog
constructors are invoked. This is repeated for Cat
and for Mammal
on lines 4–8 of the output.
Line 9 of the output represents the call to speak()
on the first object, the Dog
from line 82 (within the first for
loop). The virtual speak()
method is called, and the correct version of speak()
is invoked. The clone()
function then is called, and as this is also virtual, Dog
’s clone
function is invoked, causing the Mammal
constructor and the Dog
copy constructor to be called.
The same is repeated for Cat
on lines 12–14 of the output, and then for Mammal
on lines 15 and 16. Finally, the new array is iterated (output lines 17–19, code lines 85–88), and each of the new objects has speak()
invoked.
The difference between this approach and the use of a copy constructor is that you, as the programmer, must explicitly call the clone()
function. The copy constructor is called automatically when an object is copied. Remember that you can always override the copy function in a derived class. But that approach reduces the flexibility you have.
Because objects with virtual member functions must maintain a v-table, some overhead is required to employ them. If you have a small class from which you do not expect to derive other classes, there might be no reason to have any virtual functions at all.
After you declare any functions virtual, you’ve paid most of the price of the v-table (although each entry does add a small memory overhead). At that point, you want the destructor to be virtual, and the assumption will be that all other functions probably will also be virtual. Take a long hard look at any nonvirtual functions, and be certain you understand why they are not virtual.
Polymorphism enables the same interface to be implemented with different member functions in a set of classes related by inheritance.
This makes it possible for related objects to be used in the same manner, even if each object implements the behavior differently.
Polymorphism achieves an important goal in object-oriented programming by letting similar objects handle related functionality by reusing an interface.
Q. Why not make all class functions virtual?
A. There is overhead with the first virtual function in the creation of a v-table. After that, the overhead is trivial. Many C++ programmers feel that if one function is virtual, all others should be. Other programmers disagree, believing that there should always be a reason for what you do.
Q. If a function someFunc()
is virtual in a base class and is also overloaded so as to take either an integer or two integers, and the derived class overrides the form taking one integer, what is called when a pointer to a derived object calls the two-integer form?
A. The overriding of the one int form hides the entire base class function; thus, you will get a compile error complaining that the function requires only one int.
Q. What is the origin of “Rudolph the Red-Nosed Reindeer”?
A. “Rudolph” began as a 1939 poem by Robert May, a 34-year- copywriter for the Montgomery Wards department store in Chicago. May’s boss wanted something to give children in the store, and it became so popular that five million copies were distributed in the ‘30s and ‘40s.
The poem was written when May’s wife was seriously ill. Like Rudolph, May’s four-year-old daughter felt left out—her mother couldn’t do things with her like other mothers could.
In 1949, Johnny Marks and singer Gene Autry recorded the song based on the poem, and it became one of the top-selling singles of all time.
The reindeer almost wasn’t named Rudolph. Two other names May proposed were Rollo and Reginald.
You spent the past hour learning about polymorphism and derived classes. Now you should answer a few questions and do a couple of exercises to firm up that knowledge.
1. How does a C++ program know which virtual function to call when the objects are stored in a variable of the base class type?
A. The function has a virtual
keyword.
B. A v-table is used.
C. That’s not possible.
2. What type of method cannot be virtual?
A. Constructor
B. Desctructor
C. Clone
3. What is it called when a pointer is bound to an object at runtime, as in polymorphism?
A. Late binding
B. Static binding
C. Dereferencing
1. B. The v-table keeps track of this information for you. It is the overhead associated with this table that makes virtual functions slightly more expensive to use than regular functions.
2. A. The constructor (including the copy constructor).
3. A. Late binding is when it occurs at runtime. Static binding is when it occurs during compilation.
1. Modify the Mammal8 program
by commenting out line 21: the speak()
method within dog
. Can you think of examples where it makes sense to do this?
2. Modify the Mammal10
program to remove the virtual
on line 8 (definition of speak()
in the base class)? Can you see why the override functions are never called?
To see solutions to these activities, visit the book’s website at http://cplusplus.cadenhead.org.
18.116.15.161