Yesterday, you learned how to write virtual functions in derived classes. This is the fundamental building block of polymorphism: the capability to bind specific, derived class objects to base class pointers at runtime. Today, you learnWhat multiple inheritance is and how to use it.
Suppose you've been working with your animal classes for a while and you've divided the class hierarchy into Birds and Mammals. The Bird class includes the member function Fly(). The Mammal class has been divided into a number of types of Mammals, including Horse. The Horse class includes the member functions Whinny() and Gallop().
Suddenly, you realize you need a Pegasus object: a cross between a Horse and a Bird. A Pegasus can Fly(), it can Whinny(), and it can Gallop(). With single inheritance, you're in quite a jam.
You can make Pegasus a Bird, but then it won't be able to Whinny() or Gallop(). You can make it a Horse, but then it won't be able to Fly().
Your first solution is to copy the Fly() method into the Pegasus class and derive Pegasus from Horse. This works fine, at the cost of having the Fly() method in two places (Bird and Pegasus). If you change one, you must remember to change the other. Of course, a developer who comes along months or years later to maintain your code must also know to fix both places.
Soon, however, you have a new problem. You wish to create a list of Horse objects and a list of Bird objects. You'd like to be able to add your Pegasus objects to either list, but if a Pegasus is a horse, you can't add it to a list of birds.
You have a couple of potential solutions. You can rename the Horse method Gallop() to Move(), and then override Move() in your Pegasus object to do the work of Fly(). You would then override Move() in your other horses to do the work of Gallop(). Perhaps Pegasus could be clever enough to gallop short distances and fly longer distances.
Pegasus::Move(long distance) { if (distance > veryFar) fly(distance); else gallop(distance); }
This is a bit limiting. Perhaps one day Pegasus will want to fly a short distance or gallop a long distance. Your next solution might be to move Fly() up into Horse, as illustrated in Listing 13.1. The problem is that most horses can't fly, so you have to make this method do nothing unless it is a Pegasus.
Listing 13.1. If horses could fly...
1: // Listing 13.1. If horses could fly... 2: // Percolating Fly() up into Horse 3: 4: #include <iostream.h> 5: 6: class Horse 7: { 8: public: 9: void Gallop(){ cout << "Galloping...\n"; } 10: virtual void Fly() { cout << "Horses can't fly.\n" ; } 11: private: 12: int itsAge; 13: }; 14: 15: class Pegasus : public Horse 16: { 17: public: 18: virtual void Fly() { cout << "I can fly! I can fly! I can fly!\n"; } 19: }; 20: 21: const int NumberHorses = 5; 22: int main() 23: { 24: Horse* Ranch[NumberHorses]; 25: Horse* pHorse; 26: int choice,i; 27: for (i=0; i<NumberHorses; i++) 28: { 29: cout << "(1)Horse (2)Pegasus: "; 30: cin >> choice; 31: if (choice == 2) 32: pHorse = new Pegasus; 33: else 34: pHorse = new Horse; 35: Ranch[i] = pHorse; 36: } 37: cout << "\n"; 38: for (i=0; i<NumberHorses; i++) 39: { 40: Ranch[i]->Fly(); 41: delete Ranch[i]; 42: } 43: return 0; 44: } Output: (1)Horse (2)Pegasus: 1 (1)Horse (2)Pegasus: 2 (1)Horse (2)Pegasus: 1 (1)Horse (2)Pegasus: 2 (1)Horse (2)Pegasus: 1 Horses can't fly. I can fly! I can fly! I can fly! Horses can't fly. I can fly! I can fly! I can fly! Horses can't fly.
Analysis: This program certainly works, though at the expense of the Horse class having a Fly() method. On line 10, the method Fly() is provided to Horse. In a real-world class, you might have it issue an error, or fail quietly. On line 18, the Pegasus class overrides the Fly() method to "do the right thing," represented here by printing a happy message.
The array of Horse pointers on line 24 is used to demonstrate that the correct Fly() method is called based on the runtime binding of the Horse or Pegasus object.
Putting the required function higher in the class hierarchy is a common solution to this problem and results in many functions "percolating up" into the base class. The base class is then in grave danger of becoming a global namespace for all the functions that might be used by any of the derived classes. This can seriously undermine the class typing of C++, and can create a large and cumbersome base class.
In general, you want to percolate shared functionality up the hierarchy, without migrating the interface of each class. This means that if two classes that share a common base class (for example, Horse and Bird both share Animal) have a function in common (both birds and horses eat, for example), you'll want to move that functionality up into the base class and create a virtual function.
What you'll want to avoid, however, is percolating an interface (like Fly up where it doesn't belong), just so you can call that function only on some derived classes.
An alternative to this approach, still within single inheritance, is to keep the Fly() method within Pegasus, and only call it if the pointer is actually pointing to a Pegasus object. To make this work, you'll need to be able to ask your pointer what type it is really pointing to. This is known as Run Time Type Identification (RTTI). Using RTTI has only recently become an official part of C++.
If your compiler does not support RTTI, you can mimic it by putting a method that returns an enumerated type in each of the classes. You can then test that type at runtime and call Fly() if it returns Pegasus.
NOTE: Beware of adding RTTI to your classes. Use of it may be an indication of poor design. Consider using virtual functions, templates, or multiple inheritance instead.
In order to call Fly() however, you must cast the pointer, telling it that the object it is pointing to is a Pegasus object, not a Horse. This is called casting down, because you are casting the Horse object down to a more derived type.
C++ now officially, though perhaps reluctantly, supports casting down using the new dynamic_cast operator. Here's how it works.
If you have a pointer to a base class such as Horse, and you assign to it a pointer to a derived class, such as Pegasus, you can use the Horse pointer polymorphically. If you then need to get at the Pegasus object, you create a Pegasus pointer and use the dynamic_cast operator to make the conversion.
At runtime, the base pointer will be examined. If the conversion is proper, your new Pegasus pointer will be fine. If the conversion is improper, if you didn't really have a Pegasus object after all, then your new pointer will be null. Listing 13.2 illustrates this point.
1: // Listing 13.2 Using dynamic_cast. 2: // Using rtti 3: 4: #include <iostream.h> 5: enum TYPE { HORSE, PEGASUS }; 6: 7: class Horse 8: { 9: public: 10: virtual void Gallop(){ cout << "Galloping...\n"; } 11: 12: private: 13: int itsAge; 14: }; 15: 16: class Pegasus : public Horse 17: { 18: public: 19: 20: virtual void Fly() { cout << "I can fly! I can fly! I can fly!\n"; } 21: }; 22: 23: const int NumberHorses = 5; 24: int main() 25: { 26: Horse* Ranch[NumberHorses]; 27: Horse* pHorse; 28: int choice,i; 29: for (i=0; i<NumberHorses; i++) 30: { 31: cout << "(1)Horse (2)Pegasus: "; 32: cin >> choice; 33: if (choice == 2) 34: pHorse = new Pegasus; 35: else 36: pHorse = new Horse; 37: Ranch[i] = pHorse; 38: } 39: cout << "\n"; 40: for (i=0; i<NumberHorses; i++) 41: { 42: Pegasus *pPeg = dynamic_cast< Pegasus *> (Ranch[i]); 42: if (pPeg) 43: pPeg->Fly(); 44: else 45: cout << "Just a horse\n"; 46: 47: delete Ranch[i]; 48: } 49: return 0; 50: Output: (1)Horse (2)Pegasus: 1 (1)Horse (2)Pegasus: 2 (1)Horse (2)Pegasus: 1 (1)Horse (2)Pegasus: 2 (1)Horse (2)Pegasus: 1 Just a horse I can fly! I can fly! I can fly! Just a horse I can fly! I can fly! I can fly! Just a horse
Analysis: This solution also works. Fly() is kept out of Horse, and is not called on Horse objects. When it is called on Pegasus objects, however, they must be explicitly cast; Horse objects don't have the method Fly(), so the pointer must be told it is pointing to a Pegasus object before being used.
The need for you to cast the Pegasus object is a warning that something may be wrong with your design. This program effectively undermines the virtual function polymorphism, because it depends on casting the object to its real runtime type.
The other problem with these solutions is that you've declared Pegasus to be a type of Horse, so you cannot add a Pegasus object to a list of Birds. You've paid the price of either moving Fly() up into Horse, or casting down the pointer, and yet you still don't have the full functionality you need.
One final single inheritance solution presents itself. You can push Fly(), Whinny(), and Gallop() all up into a common base class of both Bird and Horse: Animal. Now, instead of having a list of Birds and a list of Horses, you can have one unified list of Animals. This works, but percolates more functionality up into the base classes.
Alternatively, you can leave the methods where they are, but cast down Horses and Birds and Pegasus objects, but that is even worse!
DO move functionality up the inheritance hierarchy. DON'T move interface up the inheritance hierarchy. DO avoid switching on the runtime type of the object--use virtual methods, templates, and multiple inheritance. DON'T cast pointers to base objects down to derived objects.
It is possible to derive a new class from more than one base class. This is called Multiple Inheritance. To derive from more than the base class, you separate each base class by commas in the class designation. Listing 13.3 illustrates how to declare Pegasus so that it derives from both Horses and Birds. The program then adds Pegasus objects to both types of lists.
Listing 13.3. Multiple inheritance.
1: // Listing 13.3. Multiple inheritance. 2: // Multiple Inheritance 3: 4: #include <iostream.h> 5: 6: class Horse 7: { 8: public: 9: Horse() { cout << "Horse constructor... "; } 10: virtual ~Horse() { cout << "Horse destructor... "; } 11: virtual void Whinny() const { cout << "Whinny!... "; } 12: private: 13: int itsAge; 14: }; 15: 16: class Bird 17: { 18: public: 19: Bird() { cout << "Bird constructor... "; } 20: virtual ~Bird() { cout << "Bird destructor... "; } 21: virtual void Chirp() const { cout << "Chirp... "; } 22: virtual void Fly() const 23: { 24: cout << "I can fly! I can fly! I can fly! "; 25: } 26: private: 27: int itsWeight; 28: }; 29: 30: class Pegasus : public Horse, public Bird 31: { 32: public: 33: void Chirp() const { Whinny(); } 34: Pegasus() { cout << "Pegasus constructor... "; } 35: ~Pegasus() { cout << "Pegasus destructor... "; } 36: }; 37: 38: const int MagicNumber = 2; 39: int main() 40: { 41: Horse* Ranch[MagicNumber]; 42: Bird* Aviary[MagicNumber]; 43: Horse * pHorse; 44: Bird * pBird; 45: int choice,i; 46: for (i=0; i<MagicNumber; i++) 47: { 48: cout << "\n(1)Horse (2)Pegasus: "; 49: cin >> choice; 50: if (choice == 2) 51: pHorse = new Pegasus; 52: else 53: pHorse = new Horse; 54: Ranch[i] = pHorse; 55: } 56: for (i=0; i<MagicNumber; i++) 57: { 58: cout << "\n(1)Bird (2)Pegasus: "; 59: cin >> choice; 60: if (choice == 2) 61: pBird = new Pegasus; 62: else 63: pBird = new Bird; 64: Aviary[i] = pBird; 65: } 66: 67: cout << "\n"; 68: for (i=0; i<MagicNumber; i++) 69: { 70: cout << "\nRanch[" << i << "]: " ; 71: Ranch[i]->Whinny(); 72: delete Ranch[i]; 73: } 74: 75: for (i=0; i<MagicNumber; i++) 76: { 77: cout << "\nAviary[" << i << "]: " ; 78: Aviary[i]->Chirp(); 79: Aviary[i]->Fly(); 80: delete Aviary[i]; 81: } 82: return 0; 83: } Output: (1)Horse (2)Pegasus: 1 Horse constructor... (1)Horse (2)Pegasus: 2 Horse constructor... Bird constructor... Pegasus constructor... (1)Bird (2)Pegasus: 1 Bird constructor... (1)Bird (2)Pegasus: 2 Horse constructor... Bird constructor... Pegasus constructor... Ranch[0]: Whinny!... Horse destructor... Ranch[1]: Whinny!... Pegasus destructor... Bird destructor... Horse destructor... Aviary[0]: Chirp... I can fly! I can fly! I can fly! Bird destructor... Aviary[1]: Whinny!... I can fly! I can fly! I can fly! Pegasus destructor... Bird destructor... Horse destructor... Aviary[0]: Chirp... I can fly! I can fly! I can fly! Bird destructor... Aviary[1]: Whinny!... I can fly! I can fly! I can fly! Pegasus destructor.. Bird destructor... Horse destructor...
Analysis: On lines 6-14, a Horse class is declared. The constructor and destructor print out a message, and the Whinny() method prints the word Whinny!
On lines 16-25, a Bird class is declared. In addition to its constructor and destructor, this class has two methods: Chirp() and Fly(), both of which print identifying messages. In a real program these might, for example, activate the speaker or generate animated images.
Finally, on lines 30-36, the class Pegasus is declared. It derives both from Horse and from Bird. The Pegasus class overrides the Chirp() method to call the Whinny() method, which it inherits from Horse.
Two lists are created, a Ranch with pointers to Horse on line 41, and an Aviary with pointers to Bird on line 42. On lines 46-55, Horse and Pegasus objects are added to the Ranch. On lines 56-65, Bird and Pegasus objects are added to the Aviary.
Invocations of the virtual methods on both the Bird pointers and the Horse pointers do the right things for Pegasus objects. For example, on line 78 the members of the Aviary array are used to call Chirp() on the objects to which they point. The Bird class declares this to be a virtual method, so the right function is called for each object.
Note that each time a Pegasus object is created, the output reflects that both the Bird part and the Horse part of the Pegasus object is also created. When a Pegasus object is destroyed, the Bird and Horse parts are destroyed as well, thanks to the destructors being made virtual.
Declare an object to inherit from more than one class by listing the base classes following the colon after the class name. Separate the base classes by commas. Example 1:
class Pegasus : public Horse, public Bird
Example 2:
class Schnoodle : public Schnauzer, public Poodle
When the Pegasus object is created in memory, both of
the base classes form part of the Pegasus object, as
illustrated in Figure 13.1.
Figure 13.1. Multiply
inherited objects.
A number of issues arise with objects with multiple base classes.
For example, what happens if two base classes that happen to have
the same name have virtual functions or data? How are multiple
base class constructors initialized? What happens if multiple
base classes both derive from the same class? The next sections
will answer these questions, and explore how multiple inheritance
can be put to work.
If Pegasus derives from both Horse and Bird, and each of the base classes has constructors that take parameters, the Pegasus class initializes these constructors in turn. Listing 13.4 illustrates how this is done.
Listing 13.4. Calling multiple constructors.
1: // Listing 13.4 2: // Calling multiple constructors 3: #include <iostream.h> 4: typedef int HANDS; 5: enum COLOR { Red, Green, Blue, Yellow, White, Black, Brown } ; 6: enum BOOL { FALSE, TRUE }; 7: 8: class Horse 9: { 10: public: 11: Horse(COLOR color, HANDS height); 12: virtual ~Horse() { cout << "Horse destructor...\n"; } 13: virtual void Whinny()const { cout << "Whinny!... "; } 14: virtual HANDS GetHeight() const { return itsHeight; } 15: virtual COLOR GetColor() const { return itsColor; } 16: private: 17: HANDS itsHeight; 18: COLOR itsColor; 19: }; 20: 21: Horse::Horse(COLOR color, HANDS height): 22: itsColor(color),itsHeight(height) 23: { 24: cout << "Horse constructor...\n"; 25: } 26: 27: class Bird 28: { 29: public: 30: Bird(COLOR color, BOOL migrates); 31: virtual ~Bird() {cout << "Bird destructor...\n"; } 32: virtual void Chirp()const { cout << "Chirp... "; } 33: virtual void Fly()const 34: { 35: cout << "I can fly! I can fly! I can fly! "; 36: } 37: virtual COLOR GetColor()const { return itsColor; } 38: virtual BOOL GetMigration() const { return itsMigration; } 39: 40: private: 41: COLOR itsColor; 42: BOOL itsMigration; 43: }; 44: 45: Bird::Bird(COLOR color, BOOL migrates): 46: itsColor(color), itsMigration(migrates) 47: { 48: cout << "Bird constructor...\n"; 49: } 50: 51: class Pegasus : public Horse, public Bird 52: { 53: public: 54: void Chirp()const { Whinny(); } 55: Pegasus(COLOR, HANDS, BOOL,long); 56: ~Pegasus() {cout << "Pegasus destructor...\n";} 57: virtual long GetNumberBelievers() const 58: { 59: return itsNumberBelievers; 60: } 61: 62: private: 63: long itsNumberBelievers; 64: }; 65: 66: Pegasus::Pegasus( 67: COLOR aColor, 68: HANDS height, 69: BOOL migrates, 70: long NumBelieve): 71: Horse(aColor, height), 72: Bird(aColor, migrates), 73: itsNumberBelievers(NumBelieve) 74: { 75: cout << "Pegasus constructor...\n"; 76: } 77: 78: int main() 79: { 80: Pegasus *pPeg = new Pegasus(Red, 5, TRUE, 10); 81: pPeg->Fly(); 82: pPeg->Whinny(); 83: cout << "\nYour Pegasus is " << pPeg->GetHeight(); 84: cout << " hands tall and "; 85: if (pPeg->GetMigration()) 86: cout << "it does migrate."; 87: else 88: cout << "it does not migrate."; 89: cout << "\nA total of " << pPeg->GetNumberBelievers(); 90: cout << " people believe it exists.\n"; 91: delete pPeg; 92: return 0; 93: } Output: Horse constructor... Bird constructor... Pegasus constructor... I can fly! I can fly! I can fly! Whinny!... Your Pegasus is 5 hands tall and it does migrate. A total of 10 people believe it exists. Pegasus destructor... Bird destructor... Horse destructor...
Analysis: On lines 8-19, the Horse class is declared. The constructor takes two parameters, both using enumerations declared on lines 5 and 6. The implementation of the constructor on lines 21-25 simply initializes the member variables and prints a message.
On lines 27-43, the Bird class is declared, and the implementation of its constructor is on lines 45-49. Again, the Bird class takes two parameters. Interestingly, the Horse constructor takes color (so that you can detect horses of different colors), and the Bird constructor takes the color of the feathers (so those of one feather can stick together). This leads to a problem when you want to ask the Pegasus for its color, which you'll see in the next example.
The Pegasus class itself is declared on lines 51-64, and its constructor is on lines 66-72. The initialization of the Pegasus object includes three statements. First, the Horse constructor is initialized with color and height. Then the Bird constructor is initialized with color and the Boolean. Finally, the Pegasus member variable itsNumberBelievers is initialized. Once all that is accomplished, the body of the Pegasus constructor is called.
In the main() function, a Pegasus pointer is created and used to access the member functions of the base objects.
In Listing 13.4, both the Horse class and the Bird class have a method GetColor(). You may need to ask the Pegasus object to return its color, but you have a problem: the Pegasus class inherits from both Bird and Horse. They both have a color, and their methods for getting that color have the same names and signature. This creates an ambiguity for the compiler, which you must resolve.
If you simply write
COLOR currentColor = pPeg->GetColor();
you will get a compiler error:
Member is ambiguous: `Horse::GetColor' and `Bird::GetColor'
You can resolve the ambiguity with an explicit call to the function you wish to invoke:
COLOR currentColor = pPeg->Horse::GetColor();
Anytime you need to resolve which class a member function or member data inherits from, you can fully qualify the call by prepending the class name to the base class data or function.
Note that if Pegasus were to override this function, the problem would be moved, as it should be, into the Pegasus member function:
virtual COLOR GetColor()const { return Horse::itsColor; }
This hides the problem from clients of the Pegasus class, and encapsulates within Pegasus the knowledge of which base class it wishes to inherit its color from. A client is still free to force the issue by writing:
COLOR currentColor = pPeg->Bird::GetColor();
What happens if both Bird and Horse inherit from a common base class, such as Animal? Figure 13.2 illustrates what this looks like.
As you can see in Figure 13.2, two base class objects exist.
When a function or data member is called in the shared base
class, another ambiguity exists. For example, if Animal
declares itsAge as a member variable and GetAge()
as a member function, and you call pPeg->GetAge(),
did you mean to call the GetAge() function you inherit
from Animal by way of Horse, or by way of Bird?
You must resolve this ambiguity as well, as illustrated in
Listing 13.5.
Figure 13.2. Common
base classes.
Listing 13.5. Common base classes.
1: // Listing 13.5 2: // Common base classes 3: #include <iostream.h> 4: 5: typedef int HANDS; 6: enum COLOR { Red, Green, Blue, Yellow, White, Black, Brown } ; 7: enum BOOL { FALSE, TRUE }; 8: 9: class Animal // common base to both horse and bird 10: { 11: public: 12: Animal(int); 13: virtual ~Animal() { cout << "Animal destructor...\n"; } 14: virtual int GetAge() const { return itsAge; } 15: virtual void SetAge(int age) { itsAge = age; } 16: private: 17: int itsAge; 18: }; 19: 20: Animal::Animal(int age): 21: itsAge(age) 22: { 23: cout << "Animal constructor...\n"; 24: } 25: 26: class Horse : public Animal 27: { 28: public: 29: Horse(COLOR color, HANDS height, int age); 30: virtual ~Horse() { cout << "Horse destructor...\n"; } 31: virtual void Whinny()const { cout << "Whinny!... "; } 32: virtual HANDS GetHeight() const { return itsHeight; } 33: virtual COLOR GetColor() const { return itsColor; } 34: protected: 35: HANDS itsHeight; 36: COLOR itsColor; 37: }; 38: 39: Horse::Horse(COLOR color, HANDS height, int age): 40: Animal(age), 41: itsColor(color),itsHeight(height) 42: { 43: cout << "Horse constructor...\n"; 44: } 45: 46: class Bird : public Animal 47: { 48: public: 49: Bird(COLOR color, BOOL migrates, int age); 50: virtual ~Bird() {cout << "Bird destructor...\n"; } 51: virtual void Chirp()const { cout << "Chirp... "; } 52: virtual void Fly()const 53: { cout << "I can fly! I can fly! I can fly! "; } 54: virtual COLOR GetColor()const { return itsColor; } 55: virtual BOOL GetMigration() const { return itsMigration; } 56: protected: 57: COLOR itsColor; 58: BOOL itsMigration; 59: }; 60: 61: Bird::Bird(COLOR color, BOOL migrates, int age): 62: Animal(age), 63: itsColor(color), itsMigration(migrates) 64: { 65: cout << "Bird constructor...\n"; 66: } 67: 68: class Pegasus : public Horse, public Bird 69: { 70: public: 71: void Chirp()const { Whinny(); } 72: Pegasus(COLOR, HANDS, BOOL, long, int); 73: ~Pegasus() {cout << "Pegasus destructor...\n";} 74: virtual long GetNumberBelievers() const 75: { return itsNumberBelievers; } 76: virtual COLOR GetColor()const { return Horse::itsColor; } 77: virtual int GetAge() const { return Horse::GetAge(); } 78: private: 79: long itsNumberBelievers; 80: }; 81: 82: Pegasus::Pegasus( 83: COLOR aColor, 84: HANDS height, 85: BOOL migrates, 86: long NumBelieve, 87: int age): 88: Horse(aColor, height,age), 89: Bird(aColor, migrates,age), 90: itsNumberBelievers(NumBelieve) 91: { 92: cout << "Pegasus constructor...\n"; 93: } 94: 95: int main() 96: { 97: Pegasus *pPeg = new Pegasus(Red, 5, TRUE, 10, 2); 98: int age = pPeg->GetAge(); 99: cout << "This pegasus is " << age << " years old.\n"; 100: delete pPeg; 101: return 0; 102: } Output: Animal constructor... Horse constructor... Animal constructor... Bird constructor... Pegasus constructor... This pegasus is 2 years old. Pegasus destructor... Bird destructor... Animal destructor... Horse destructor... Animal destructor...
Analysis: There are a number of interesting features to this listing. The Animal class is declared on lines 9-18. Animal adds one member variable, itsAge and an accessor, SetAge().
On line 26, the Horse class is declared to derive from Animal. The Horse constructor now has a third parameter, age, which it passes to its base class, Animal. Note that the Horse class does not override GetAge(), it simply inherits it.
On line 46, the Bird class is declared to derive from Animal. Its constructor also takes an age and uses it to initialize its base class, Animal. It also inherits GetAge() without overriding it.
Pegasus inherits from both Bird and from Animal, and so has two Animal classes in its inheritance chain. If you were to call GetAge() on a Pegasus object, you would have to disambiguate, or fully qualify, the method you want if Pegasus did not override the method.
This is solved on line 76 when the Pegasus object overrides GetAge() to do nothing more than to chain up--that is, to call the same method in a base class.
Chaining up is done for two reasons: either to disambiguate which base class to call, as in this case, or to do some work and then let the function in the base class do some more work. At times, you may want to do work and then chain up, or chain up and then do the work when the base class function returns.
The Pegasus constructor takes five parameters: the creature's color, its height (in HANDS), whether or not it migrates, how many believe in it, and its age. The constructor initializes the Horse part of the Pegasus with the color, height, and age on line 88. It initializes the Bird part with color, whether it migrates, and age on line 89. Finally, it initializes itsNumberBelievers on line 90.
The call to the Horse constructor on line 88 invokes the implementation shown on line 39. The Horse constructor uses the age parameter to initialize the Animal part of the Horse part of the Pegasus. It then goes on to initialize the two member variables of Horse--itsColor and itsAge.
The call to the Bird constructor on line 89 invokes the implementation shown on line 46. Here too, the age parameter is used to initialize the Animal part of the Bird.
Note that the color parameter to the Pegasus is used to initialize member variables in each of Bird and Horse. Note also that the age is used to initialize itsAge in the Horse's base Animal and in the Bird's base Animal.
In Listing 13.5, the Pegasus class went to some lengths to disambiguate which of its Animal base classes it meant to invoke. Most of the time, the decision as to which one to use is arbitrary--after all, the Horse and the Bird have exactly the same base class.
It is possible to tell C++ that you do not want two copies of the shared base class, as shown in Figure 13.2, but rather to have a single shared base class, as shown in Figure 13.3.
You accomplish this by making Animal a virtual base class of both Horse and Bird. The Animal class does not change at all. The Horse and Bird classes change only in their use of the term virtual in their declarations. Pegasus, however, changes substantially.
Normally, a class's constructor initializes only its own variables and its base class. Virtually inherited base classes are an exception, however. They are initialized by their most derived class. Thus, Animal is initialized not by Horse and Bird, but by Pegasus. Horse and Bird have to initialize Animal in their constructors, but these initializations will be ignored when a Pegasus object is created.
Listing 13.6 rewrites Listing 13.5 to take advantage of
virtual derivation.
Figure 13.3. A
diamond inheritance.
Listing 13.6. Illustration of the use of virtual inheritance.
1: // Listing 13.6 2: // Virtual inheritance 3: #include <iostream.h> 4: 5: typedef int HANDS; 6: enum COLOR { Red, Green, Blue, Yellow, White, Black, Brown } ; 7: enum BOOL { FALSE, TRUE }; 8: 9: class Animal // common base to both horse and bird 10: { 11: public: 12: Animal(int); 13: virtual ~Animal() { cout << "Animal destructor...\n"; } 14: virtual int GetAge() const { return itsAge; } 15: virtual void SetAge(int age) { itsAge = age; } 16: private: 17: int itsAge; 18: }; 19: 20: Animal::Animal(int age): 21: itsAge(age) 22: { 23: cout << "Animal constructor...\n"; 24: } 25: 26: class Horse : virtual public Animal 27: { 28: public: 29: Horse(COLOR color, HANDS height, int age); 30: virtual ~Horse() { cout << "Horse destructor...\n"; } 31: virtual void Whinny()const { cout << "Whinny!... "; } 32: virtual HANDS GetHeight() const { return itsHeight; } 33: virtual COLOR GetColor() const { return itsColor; } 34: protected: 35: HANDS itsHeight; 36: COLOR itsColor; 37: }; 38: 39: Horse::Horse(COLOR color, HANDS height, int age): 40: Animal(age), 41: itsColor(color),itsHeight(height) 42: { 43: cout << "Horse constructor...\n"; 44: } 45: 46: class Bird : virtual public Animal 47: { 48: public: 49: Bird(COLOR color, BOOL migrates, int age); 50: virtual ~Bird() {cout << "Bird destructor...\n"; } 51: virtual void Chirp()const { cout << "Chirp... "; } 52: virtual void Fly()const 53: { cout << "I can fly! I can fly! I can fly! "; } 54: virtual COLOR GetColor()const { return itsColor; } 55: virtual BOOL GetMigration() const { return itsMigration; } 56: protected: 57: COLOR itsColor; 58: BOOL itsMigration; 59: }; 60: 61: Bird::Bird(COLOR color, BOOL migrates, int age): 62: Animal(age), 63: itsColor(color), itsMigration(migrates) 64: { 65: cout << "Bird constructor...\n"; 66: } 67: 68: class Pegasus : public Horse, public Bird 69: { 70: public: 71: void Chirp()const { Whinny(); } 72: Pegasus(COLOR, HANDS, BOOL, long, int); 73: ~Pegasus() {cout << "Pegasus destructor...\n";} 74: virtual long GetNumberBelievers() const 75: { return itsNumberBelievers; } 76: virtual COLOR GetColor()const { return Horse::itsColor; } 77: private: 78: long itsNumberBelievers; 79: }; 80: 81: Pegasus::Pegasus( 82: COLOR aColor, 83: HANDS height, 84: BOOL migrates, 85: long NumBelieve, 86: int age): 87: Horse(aColor, height,age), 88: Bird(aColor, migrates,age), 89: Animal(age*2), 90: itsNumberBelievers(NumBelieve) 91: { 92: cout << "Pegasus constructor...\n"; 93: } 94: 95: int main() 96: { 97: Pegasus *pPeg = new Pegasus(Red, 5, TRUE, 10, 2); 98: int age = pPeg->GetAge(); 99: cout << "This pegasus is " << age << " years old.\n"; 100: delete pPeg; 101: return 0; 102: } Output: Animal constructor... Horse constructor... Bird constructor... Pegasus constructor... This pegasus is 4 years old. Pegasus destructor... Bird destructor... Horse destructor... Animal destructor...
Analysis: On line 26, Horse declares that it inherits virtually from Animal, and on line 46, Bird makes the same declaration. Note that the constructors for both Bird and Animal still initialize the Animal object.
Pegasus inherits from both Bird and Animal, and as the most derived object of Animal, it also initializes Animal. It is Pegasus' initialization which is called, however, and the calls to Animal's constructor in Bird and Horse are ignored. You can see this because the value 2 is passed in, and Horse and Bird pass it along to Animal, but Pegasus doubles it. The result, 4, is reflected in the printout on line 99 and as shown in the output.
Pegasus no longer has to disambiguate the call to GetAge(), and so is free to simply inherit this function from Animal. Note that Pegasus must still disambiguate the call to GetColor(), as this function is in both of its base classes and not in Animal.
To ensure that derived classes have only one instance of common base classes, declare the intermediate classes to inherit virtually from the base class. Example 1:
class Horse : virtual public Animal class Bird : virtual public Animal class Pegasus : public Horse, public Bird
Example 2:
class Schnauzer : virtual public Dog class Poodle : virtual public Dog class Schnoodle : public Schnauzer, public Poodle
Although multiple inheritance offers a number of advantages over single inheritance, there are many C++ programmers who are reluctant to use it. The problems they cite are that many compilers don't support it yet, that it makes debugging harder, and that nearly everything that can be done with multiple inheritance can be done without it.
These are valid concerns, and you will want to be on your guard against installing needless complexity into your programs. Some debuggers have a hard time with multiple inheritance, and some designs are needlessly made complex by using multiple inheritance when it is not needed.
DO use multiple inheritance when a new class needs functions and features from more than one base class. DO use virtual inheritance when the most derived classes must have only one instance of the shared base class. DO initialize the shared base class from the most derived class when using virtual base classes. DON'T use multiple inheritance when single inheritance will do.
One way to strike a middle ground between multiple inheritance and single inheritance is to use what are called mixins. Thus, you might have your Horse class derive from Animal and from Displayable. Displayable would just add a few methods for displaying any object onscreen.
New Term: A mixin , or capability class, is a class that adds functionality without adding much or any data.
Capability classes are mixed into a derived class like any other class might be, by declaring the derived class to inherit publicly from them. The only difference between a capability class and any other class is that the capability class has little or no data. This is an arbitrary distinction, of course, and is just a shorthand way of noting that at times all you want to do is mix in some additional capabilities without complicating the derived class.
This will, for some debuggers, make it easier to work with mixins than with more complex multiply inherited objects. There is also less likelihood of ambiguity in accessing the data in the other principal base class.
For example, if Horse derives from Animal and from Displayable, Displayable would have no data. Animal would be just as it always was, so all the data in Horse would derive from Animal, but the functions in Horse would derive from both.
The term mixin comes from an ice-cream store in Sommerville, Massachusetts, where candies and cakes were mixed into the basic ice-cream flavors. This seemed like a good metaphor to some of the object-oriented programmers who used to take a summer break there, especially while working with the object-oriented programming language SCOOPS.
Often, you will create a hierarchy of classes together. For example, you might create a Shape class, and derive from that Rectangle and Circle. From Rectangle, you might derive Square, as a special case of Rectangle.
Each of the derived classes will override the Draw() method, the GetArea() method, and so forth. Listing 13.7 illustrates a bare-bones implementation of the Shape class and its derived Circle and Rectangle classes.
1: //Listing 13.7. Shape classes. 2: 3: #include <iostream.h> 4: 5: enum BOOL { FALSE, TRUE }; 6: 7: class Shape 8: { 9: public: 10: Shape(){} 11: ~Shape(){} 12: virtual long GetArea() { return -1; } // error 13: virtual long GetPerim() { return -1; } 14: virtual void Draw() {} 15: private: 16: }; 17: 18: class Circle : public Shape 19: { 20: public: 21: Circle(int radius):itsRadius(radius){} 22: ~Circle(){} 23: long GetArea() { return 3 * itsRadius * itsRadius; } 24: long GetPerim() { return 9 * itsRadius; } 25: void Draw(); 26: private: 27: int itsRadius; 28: int itsCircumference; 29: }; 30: 31: void Circle::Draw() 32: { 33: cout << "Circle drawing routine here!\n"; 34: } 35: 36: 37: class Rectangle : public Shape 38: { 39: public: 40: Rectangle(int len, int width): 41: itsLength(len), itsWidth(width){} 42: ~Rectangle(){} 43: virtual long GetArea() { return itsLength * itsWidth; } 44: virtual long GetPerim() {return 2*itsLength + 2*itsWidth; } 45: virtual int GetLength() { return itsLength; } 46: virtual int GetWidth() { return itsWidth; } 47: virtual void Draw(); 48: private: 49: int itsWidth; 50: int itsLength; 51: }; 52: 53: void Rectangle::Draw() 54: { 55: for (int i = 0; i<itsLength; i++) 56: { 57: for (int j = 0; j<itsWidth; j++) 58: cout << "x "; 59: 60: cout << "\n"; 61: } 62: } 63: 64: class Square : public Rectangle 65: { 66: public: 67: Square(int len); 68: Square(int len, int width); 69: ~Square(){} 70: long GetPerim() {return 4 * GetLength();} 71: }; 72: 73: Square::Square(int len): 74: Rectangle(len,len) 75: {} 76: 77: Square::Square(int len, int width): 78: Rectangle(len,width) 79: 80: { 81: if (GetLength() != GetWidth()) 82: cout << "Error, not a square... a Rectangle??\n"; 83: } 84: 85: int main() 86: { 87: int choice; 88: BOOL fQuit = FALSE; 89: Shape * sp; 90: 91: while (1) 92: { 93: cout << "(1)Circle (2)Rectangle (3)Square (0)Quit: "; 94: cin >> choice; 95: 96: switch (choice) 97: { 98: case 1: sp = new Circle(5); 99: break; 100: case 2: sp = new Rectangle(4,6); 101: break; 102: case 3: sp = new Square(5); 103: break; 104: default: fQuit = TRUE; 105: break; 106: } 107: if (fQuit) 108: break; 109: 110: sp->Draw(); 111: cout << "\n"; 112: } 113: return 0; 114: } Output: (1)Circle (2)Rectangle (3)Square (0)Quit: 2 x x x x x x x x x x x x x x x x x x x x x x x x (1)Circle (2)Rectangle (3)Square (0)Quit:3 x x x x x x x x x x x x x x x x x x x x x x x x x (1)Circle (2)Rectangle (3)Square (0)Quit:0
Analysis: On lines 7-16, the Shape class is declared. The GetArea() and GetPerim() methods return an error value, and Draw() takes no action. After all, what does it mean to draw a Shape? Only types of shapes (circles, rectangle, and so on) can be drawn, Shapes as an abstraction cannot be drawn.
Circle derives from Shape and overrides the three virtual methods. Note that there is no reason to add the word "virtual," as that is part of their inheritance. But there is no harm in doing so either, as shown in the Rectangle class on lines 43, 44, and 47. It is a good idea to include the term virtual as a reminder, a form of documentation.
Square derives from Rectangle, and it too overrides the GetPerim() method, inheriting the rest of the methods defined in Rectangle.
It is troubling, though, that a client might try to instantiate a Shape object, and it might be desirable to make that impossible. The Shape class exists only to provide an interface for the classes derived from it; as such it is an Abstract Data Type, or ADT.
New Term: An Abstract Data Type represents a concept (like shape) rather than an object (like circle). In C++, an ADT is always the base class to other classes, and it is not valid to make an instance of an ADT.
C++ supports the creation of abstract data types with pure virtual functions. A virtual function ismade pure by initializing it with zero, as in
virtual void Draw() = 0;
Any class with one or more pure virtual functions is an ADT, and it is illegal to instantiate an object of a class that is an ADT. Trying to do so will cause a compile-time error. Putting a pure virtual function in your class signals two things to clients of your class:
Any class that derives from an ADT inherits the pure virtual function as pure, and so must override every pure virtual function if it wants to instantiate objects. Thus, if Rectangle inherits from Shape, and Shape has three pure virtual functions, Rectangle must override all three or it too will be an ADT. Listing 13.8 rewrites the Shape class to be an abstract data type. To save space, the rest of Listing 13.7 is not reproduced here. Replace the declaration of Shape in Listing 13.7, lines 7-16, with the declaration of Shape in Listing 13.8 and run the program again.
Listing 13.8. Abstract Data Types.
1: class Shape 2: { 3: public: 4: Shape(){} 5: ~Shape(){} 6: virtual long GetArea() = 0; // error 7: virtual long GetPerim()= 0; 8: virtual void Draw() = 0; 9: private:
10: };
Output: (1)Circle (2)Rectangle (3)Square (0)Quit: 2 x x x x x x x x x x x x x x x x x x x x x x x x (1)Circle (2)Rectangle (3)Square (0)Quit: 3 x x x x x x x x x x x x x x x x x x x x x x x x x (1)Circle (2)Rectangle (3)Square (0)Quit: 0
Analysis: As you can see, the workings of the program are totally unaffected. The only difference is that it would now be impossible to make an object of class Shape.
Declare a class to be an abstract data type by including one or more pure virtual functions in the class declaration. Declare a pure virtual function by writing = 0 after the function declaration. Example:
class Shape { virtual void Draw() = 0; // pure virtual };
Typically, the pure virtual functions in an abstract base class are never implemented. Because no objects of that type are ever created, there is no reason to provide implementations, and the ADT works purely as the definition of an interface to objects which derive from it.
It is possible, however, to provide an implementation to a pure virtual function. The function can then be called by objects derived from the ADT, perhaps to provide common functionality to all the overridden functions. Listing 13.9 reproduces Listing 13.7, this time with Shape as an ADT and with an implementation for the pure virtual function Draw(). The Circle class overrides Draw(), as it must, but it then chains up to the base class function for additional functionality.
In this example, the additional functionality is simply an additional message printed, but one can imagine that the base class provides a shared drawing mechanism, perhaps setting up a window that all derived classes will use.
Listing 13.9. Implementing pure virtual functions.
1: //Implementing pure virtual functions 2: 3: #include <iostream.h> 4: 5: enum BOOL { FALSE, TRUE }; 6: 7: class Shape 8: { 9: public: 10: Shape(){} 11: ~Shape(){} 12: virtual long GetArea() = 0; // error 13: virtual long GetPerim()= 0; 14: virtual void Draw() = 0; 15: private: 16: }; 17: 18: void Shape::Draw() 19: { 20: cout << "Abstract drawing mechanism!\n"; 21: } 22: 23: class Circle : public Shape 24: { 25: public: 26: Circle(int radius):itsRadius(radius){} 27: ~Circle(){} 28: long GetArea() { return 3 * itsRadius * itsRadius; } 29: long GetPerim() { return 9 * itsRadius; } 30: void Draw(); 31: private: 32: int itsRadius; 33: int itsCircumference; 34: }; 35: 36: void Circle::Draw() 37: { 38: cout << "Circle drawing routine here!\n"; 39: Shape::Draw(); 40: } 41: 42: 43: class Rectangle : public Shape 44: { 45: public: 46: Rectangle(int len, int width): 47: itsLength(len), itsWidth(width){} 48: ~Rectangle(){} 49: long GetArea() { return itsLength * itsWidth; } 50: long GetPerim() {return 2*itsLength + 2*itsWidth; } 51: virtual int GetLength() { return itsLength; } 52: virtual int GetWidth() { return itsWidth; } 53: void Draw(); 54: private: 55: int itsWidth; 56: int itsLength; 57: }; 58: 59: void Rectangle::Draw() 60: { 61: for (int i = 0; i<itsLength; i++) 62: { 63: for (int j = 0; j<itsWidth; j++) 64: cout << "x "; 65: 66: cout << "\n"; 67: } 68: Shape::Draw(); 69: } 70: 71: 72: class Square : public Rectangle 73: { 74: public: 75: Square(int len); 76: Square(int len, int width); 77: ~Square(){} 78: long GetPerim() {return 4 * GetLength();} 79: }; 80: 81: Square::Square(int len): 82: Rectangle(len,len) 83: {} 84: 85: Square::Square(int len, int width): 86: Rectangle(len,width) 87: 88: { 89: if (GetLength() != GetWidth()) 90: cout << "Error, not a square... a Rectangle??\n"; 91: } 92: 93: int main() 94: { 95: int choice; 96: BOOL fQuit = FALSE; 97: Shape * sp; 98: 99: while (1) 100: { 101: cout << "(1)Circle (2)Rectangle (3)Square (0)Quit: "; 102: cin >> choice; 103: 104: switch (choice) 105: { 106: case 1: sp = new Circle(5); 107: break; 108: case 2: sp = new Rectangle(4,6); 109: break; 110: case 3: sp = new Square (5); 111: break; 112: default: fQuit = TRUE; 113: break; 114: } 115: if (fQuit) 116: break; 117: 118: sp->Draw(); 119: cout << "\n"; 120: } 121: return 0; 122: } Output: (1)Circle (2)Rectangle (3)Square (0)Quit: 2 x x x x x x x x x x x x x x x x x x x x x x x x Abstract drawing mechanism! (1)Circle (2)Rectangle (3)Square (0)Quit: 3 x x x x x x x x x x x x x x x x x x x x x x x x x Abstract drawing mechanism! (1)Circle (2)Rectangle (3)Square (0)Quit: 0
Analysis: On lines 7-16, the abstract data type Shape is declared, with all three of its accessor methods declared to be pure virtual. Note that this is not necessary. If any one were declared pure virtual, the class would have been an ADT.
The GetArea() and GetPerim() methods are not implemented, but Draw() is. Circle and Rectangle both override Draw(), and both chain up to the base method, taking advantage of shared functionality in the base class.
At times, you will derive ADTs from other ADTs. It may be that you will want to make some of the derived pure virtual functions non-pure, and leave others pure.
If you create the Animal class, you may make Eat(), Sleep(), Move(), and Reproduce() all be pure virtual functions. Perhaps from Animal you derive Mammal and Fish.
On examination, you decide that every Mammal will reproduce in the same way, and so you make Mammal::Reproduce() be non-pure, but you leave Eat(), Sleep(), and Move() as pure virtual functions.
From Mammal you derive Dog, and Dog must override and implement the three remaining pure virtual functions so that you can make objects of type Dog.
What you've said, as class designer, is that no Animals or Mammals can be instantiated, but that all Mammals may inherit the provided Reproduce() method without overriding it.
Listing 13.10 illustrates this technique with a bare-bones implementation of these classes.
Listing 13.10. Deriving ADTs from other ADTs.
1: // Listing 13.10 2: // Deriving ADTs from other ADTs 3: #include <iostream.h> 4: 5: enum COLOR { Red, Green, Blue, Yellow, White, Black, Brown } ; 6: enum BOOL { FALSE, TRUE }; 7: 8: class Animal // common base to both horse and bird 9: { 10: public: 11: Animal(int); 12: virtual ~Animal() { cout << "Animal destructor...\n"; } 13: virtual int GetAge() const { return itsAge; } 14: virtual void SetAge(int age) { itsAge = age; } 15: virtual void Sleep() const = 0; 16: virtual void Eat() const = 0; 17: virtual void Reproduce() const = 0; 18: virtual void Move() const = 0; 19: virtual void Speak() const = 0; 20: private: 21: int itsAge; 22: }; 23: 24: Animal::Animal(int age): 25: itsAge(age) 26: { 27: cout << "Animal constructor...\n"; 28: } 29: 30: class Mammal : public Animal 31: { 32: public: 33: Mammal(int age):Animal(age) 34: { cout << "Mammal constructor...\n";} 35: ~Mammal() { cout << "Mammal destructor...\n";} 36: virtual void Reproduce() const 37: { cout << "Mammal reproduction depicted...\n"; } 38: }; 39: 40: class Fish : public Animal 41: { 42: public: 43: Fish(int age):Animal(age) 44: { cout << "Fish constructor...\n";} 45: virtual ~Fish() {cout << "Fish destructor...\n"; } 46: virtual void Sleep() const { cout << "fish snoring...\n"; } 47: virtual void Eat() const { cout << "fish feeding...\n"; } 48: virtual void Reproduce() const 49: { cout << "fish laying eggs...\n"; } 50: virtual void Move() const 51: { cout << "fish swimming...\n"; } 52: virtual void Speak() const { } 53: }; 54: 55: class Horse : public Mammal 56: { 57: public: 58: Horse(int age, COLOR color ): 59: Mammal(age), itsColor(color) 60: { cout << "Horse constructor...\n"; } 61: virtual ~Horse() { cout << "Horse destructor...\n"; } 62: virtual void Speak()const { cout << "Whinny!... \n"; } 63: virtual COLOR GetItsColor() const { return itsColor; } 64: virtual void Sleep() const 65: { cout << "Horse snoring...\n"; } 66: virtual void Eat() const { cout << "Horse feeding...\n"; } 67: virtual void Move() const { cout << "Horse running...\n";} 68: 69: protected: 70: COLOR itsColor; 71: }; 72: 73: class Dog : public Mammal 74: { 75: public: 76: Dog(int age, COLOR color ): 77: Mammal(age), itsColor(color) 78: { cout << "Dog constructor...\n"; } 79: virtual ~Dog() { cout << "Dog destructor...\n"; } 80: virtual void Speak()const { cout << "Whoof!... \n"; } 81: virtual void Sleep() const { cout << "Dog snoring...\n"; } 82: virtual void Eat() const { cout << "Dog eating...\n"; } 83: virtual void Move() const { cout << "Dog running...\n"; } 84: virtual void Reproduce() const 85: { cout << "Dogs reproducing...\n"; } 86: 87: protected: 88: COLOR itsColor; 89: }; 90: 91: int main() 92: { 93: Animal *pAnimal=0; 94: int choice; 95: BOOL fQuit = FALSE; 96: 97: while (1) 98: { 99: cout << "(1)Dog (2)Horse (3)Fish (0)Quit: "; 100: cin >> choice; 101: 102: switch (choice) 103: { 104: case 1: pAnimal = new Dog(5,Brown); 105: break; 106: case 2: pAnimal = new Horse(4,Black); 107: break; 108: case 3: pAnimal = new Fish (5); 109: break; 110: default: fQuit = TRUE; 111: break; 112: } 113: if (fQuit) 114: break; 115: 116: pAnimal->Speak(); 117: pAnimal->Eat(); 118: pAnimal->Reproduce(); 119: pAnimal->Move(); 120: pAnimal->Sleep(); 121: delete pAnimal; 122: cout << "\n"; 123: } 124: return 0 125: } Output: (1)Dog (2)Horse (3)Bird (0)Quit: 1 Animal constructor... Mammal constructor... Dog constructor... Whoof!... Dog eating... Dog reproducing.... Dog running... Dog snoring... Dog destructor... Mammal destructor... Animal destructor... (1)Dog (2)Horse (3)Bird (0)Quit: 0
Analysis: On lines 8-22, the abstract data type Animal is declared. Animal has non-pure virtual accessors for itsAge, which are shared by all Animal objects. It has five pure virtual functions, Sleep(), Eat(), Reproduce(), Move(), and Speak().
Mammal is derived from Animal, is declared on lines 30-38, and adds no data. It overrides Reproduce(), however, providing a common form of reproduction for all mammals. Fish must override Reproduce(), because Fish derives directly from Animal and cannot take advantage of Mammalian reproduction (and a good thing, too!).
Mammal classes no longer have to override the Reproduce() function, but they are free to do so if they choose, as Dog does on line 84. Fish, Horse, and Dog all override the remaining pure virtual functions, so that objects of their type can be instantiated.
In the body of the program, an Animal pointer is used to point to the various derived objects in turn. The virtual methods are invoked, and based on the runtime binding of the pointer, the correct method is called in the derived class.
It would be a compile-time error to try to instantiate an Animal or a Mammal, as both are abstract data types.
In one program, the class Animal is abstract, in another it is not. What determines whether to make a class abstract or not?
The answer to this question is decided not by any real-world intrinsic factor, but by what makes sense in your program. If you are writing a program that depicts a farm or a zoo, you may want Animal to be an abstract data type, but Dog to be a class from which you can instantiate objects.
On the other hand, if you are making an animated kennel, you may want to keep Dog as an abstract data type, and only instantiate types of dogs: retrievers, terriers, and so fort. The level of abstraction is a function of how finely you need to distinguish your types.
DO use abstract data types to provide common functionality for a number of related classes. DO override all pure virtual functions. DO make pure virtual any function that must be overridden. DON'T try to instantiate an object of an abstract data type.
A very hot trend in C++ is the creation and dissemination of design patterns. These are well- documented solutions to common problems encountered by C++ programmers. As an example, the observer pattern solves a common problem in inheritance.
Imagine you develop a timer class which knows how to count elapsed seconds. Such a class might have a class member itsSeconds which is an integer, and it would have methods to set, get, and increment itsSeconds.
Now let's further assume that your program wants to be informed every time the timer's itsSeconds member is incremented. One obvious solution would be to put a notification method into the timer. However, notification is not an intrinsic part of timing, and the complex code for registering those classes which need to be informed when the clock increments doesn't really belong in your timer class.
More importantly, once you work out the logic of registering those who are interested in these changes, and then notifying them, you'd like to abstract this out into a class of its own and be able to reuse it with other classes which might be "observed" in this way.
Therefore, a better solution is to create an observer class. Make this observer an Abstract Data Type with a pure virtual function Update().
Now create a second abstract data type, called Subject. Subject keeps an array of Observer objects and also provides two methods: register() (which adds observers to its list) and Notify(), which is called when there is something to report.
Those classes which wish to be notified of your timer's changes inherit from Observer. The timer itself inherits from Subject. The Observer class registers itself with the Subject class. The Subject class calls Notify when it changes (in this case when the timer updates).
Finally, we note that not every client of timer wants to be observable, and thus we create a new class called ObservedTimer, which inherits both from timer and from Subject. This gives the ObservedTimer the timer characteristics and the ability to be observed.
Many C++ programmers are aware that Java was based in large part on C++, and yet the creators of Java chose to leave out multiple inheritance. It was their opinion that multiple inheritance introduced complexity that worked against the ease of use of Java. They felt they could meet 90% of the multiple inheritance functionality using what are called interfaces.
New Term: An interface is much like an Abstract Data Type in that it defines a set of functions that can only be implemented in a derived class. However, with interfaces, you don't directly derive from the interface, you derive from another class and implement the interface, much like multiple inheritance. Thus, this marriage of an abstract data type and multiple inheritance gives you something akin to a capability class without the complexity or overhead of multiple inheritance. In addition, because interfaces cannot have implementations nor data members, the need for virtual inheritance is eliminated.
Whether this is a bug or a feature is in the eyes of the beholder. In either case, if you understand multiple inheritance and Abstract Data Types in C++ you will be in a good position to move on to using some of the more advanced features of Java should you decide to learn that language as well.
The observer pattern and how it is implemented both in Java and C++ is covered in detail in Robert Martin's article "C++ and Java: A Critical Comparison," in the January 1997 issue of C++ Report.
Today you learned how to overcome some of the limitations in single inheritance. You learned about the danger of percolating interfaces up the inheritance hierarchy, and the risks in casting down the inheritance hierarchy. You also learned how to use multiple inheritance, what problems multiple inheritance can create and how to solve them using virtual inheritance.
You also learned what Abstract Data Types are and how to create Abstract classes using pure virtual functions. You learned how to implement pure virtual functions and when and why you might do so. Finally, you saw how to implement the Observer Pattern using multiple inheritance and Abstract Data types.
The Workshop provides quiz questions to help you solidify your understanding of the material covered, and exercises to provide you with experience in using what you've learned. Try to answer the quiz and exercise questions before checking the answers in Appendix D, and make sure you understand the answers before continuing to the next chapter.