Problems with Single Inheritance

In the previous chapters, I discussed treating derived objects polymorphically with their base classes. You saw that if the base class has a method Speak() that is overridden in the derived class, a pointer to a base object that is assigned to a derived object will “do the right thing.” Listing 18.1 illustrates this idea.

Listing 18.1. Virtual Methods
 0:  // Listing 18.1 - virtual methods
 1:  #include <iostream>
 2:
 3:  class Mammal
 4:  {
 5:  public:
 6:      Mammal():itsAge(1) { std::cout << "Mammal constructor...
"; }
 7:      virtual ~Mammal() { std::cout << "Mammal destructor...
"; }
 8:      virtual void Speak() const { std::cout << "Mammal speak!
"; }
 9:  protected:
10:      int itsAge;
11:  };
12:
13:  class Cat: public Mammal
14:  {
15:  public:
16:      Cat() { std::cout << "Cat constructor...
"; }
17:      ~Cat() { std::cout << "Cat destructor...
"; }
18:      void Speak()const { std::cout << "Meow!
"; }
19:  };
20:
21:  int main()
22:  {
23:      Mammal *pCat = new Cat;
24:      pCat->Speak();
25:      return 0;
26:  }


Mammal constructor...
Cat Constructor...
Meow!
					

On line 8, Speak() is declared to be a virtual method; it is overridden on line 18 and invoked on line 24. Note, again, that pCat is declared to be a pointer to Mammal, but the address of a Cat is assigned to it. This is, as discussed in Hour 17, “Polymorphism and Derived Classes,” the essence of polymorphism.


What happens, however, if you want to add a method to Cat that is inappropriate for Mammal? For example, suppose you want to add a method called Purr(). Cats purr, but no other mammals do. You would declare your class like this:

class Cat: public Mammal
{
public:
   Cat() { std::cout << "Cat constructor...
"; }
   ~Cat() { std::cout << "Cat destructor...
"; }
  void Speak()const { std::cout << "Meow
"; }
  void Purr()const { std::cout << "rrrrrrrrrrrrrrrr
"; }
};

The problem is this: If you now call Purr() using your pointer to Mammal, you will get a compile error. When you call Purr(), your compiler will reply:

error C2039: 'Purr' : is not a member of 'Mammal'

Compilers vary in their error message. The Bloodshed compiler, for example, gives this error message:

No matching function for call to 'Mammal::Purr()'


When your compiler tries to resolve Purr() in its Mammal virtual table, there will be no entry. You can percolate this method up into the base class, but that is a very bad idea. Although it will work as an expedient, populating your base class with methods that are specific to derived classes is poor programming practice and a recipe for difficult-to-maintain code.

In fact, this entire problem is a reflection of bad design. Generally, if you have a pointer to a base class that is assigned to a derived class object, it is because you intend to use that object polymorphically, and in this case, you ought not even try to access methods that are specific to the derived class.

Let me be clear: The problem is not that you have such specific methods; it is that you are trying to get at them with the base class pointer. In an ideal world, when you have such a pointer you would not try to get at those methods.

But this is not an ideal world, and at times, you find yourself with a collection of base objects—for example, a zoo full of mammals. At one point or another, you might realize you have a Cat object and you want the darn thing to purr. In this case, there might be only one thing to do: cheat.

This is the way you cheat: You cast your base class pointer to your derived type. You say to the compiler, “Look, Bub, I happen to know this is really a cat, so shaddup and do what I tell you.” You have to sound like a thug when you say it, because you are acting like one; you are essentially extorting Cat behavior out of a Mammal pointer.

To make this work, you'll use the dynamic_cast operator. This operator ensures that when you cast, you cast safely. Further, it helps you quickly find those places in your code where you have used this feature, so that you can remove it as soon as you come to your senses.

Here's how it works: If you have a pointer to a base class, such as Mammal, and you assign to it a pointer to a derived class, such as Cat, you can use the Mammal pointer polymorphically to access virtual functions. Then, if you need to get at the Cat object to call, for example, the purr() method, you create a Cat pointer using the dynamic_cast operator to do so. At runtime, the base pointer will be examined. If the conversion is proper, your new Cat pointer will be fine. If the conversion is improper, if you didn't really have a Cat object after all, your new pointer will be null. Listing 18.2 illustrates this.

Listing 18.2. Dynamic Cast
 0:  // Listing 18.2 - dynamic cast
 1:  #include <iostream>
 2:  using std::cout; // this file uses std::cout
 3:
 4:  class Mammal
 5:  {
 6:  public:
 7:      Mammal():itsAge(1) { cout << "Mammal constructor...
"; }
 8:      virtual ~Mammal() { cout << "Mammal destructor...
"; }
 9:      virtual void Speak() const { cout << "Mammal speak!
"; }
10:  protected:
11:      int itsAge;
12:  };
13:
14:  class Cat: public Mammal
15:  {
16:  public:
17:      Cat() { cout << "Cat constructor...
"; }
18:      ~Cat() { cout << "Cat destructor...
"; }
19:      void Speak()const { cout << "Meow
"; }
20:      void Purr() const { cout << "rrrrrrrrrrr
"; }
21:  };
22:
23:  class Dog: public Mammal
24:  {
25:  public:
26:      Dog() { cout << "Dog Constructor...
"; }
27:      ~Dog() { cout << "Dog destructor...
"; }
28:      void Speak()const { cout << "Woof!
"; }
29:  };
30:
31:
32:  int main()
33:  {
34:      const int NumberMammals = 3;
35:      Mammal* Zoo[NumberMammals];
36:      Mammal* pMammal;
37:      int choice,i;
38:      for (i=0; i<NumberMammals; i++)
39:      {
40:          cout << "(1)Dog (2)Cat: ";
41:          std::cin >> choice;
42:          if (choice == 1)
43:              pMammal = new Dog;
44:          else
45:              pMammal = new Cat;
46:
47:          Zoo[i] = pMammal;
48:      }
49:
50:      cout << "
";
51:
52:      for (i=0; i<NumberMammals; i++)
53:      {
54:          Zoo[i]->Speak();
55:
56:          Cat *pRealCat =  dynamic_cast<Cat *> (Zoo[i]);
57:
58:          if (pRealCat)
59:              pRealCat->Purr();
60:          else
61:              cout << "Uh oh, not a cat!
";
62:
63:          delete Zoo[i];
64:          cout << "
";
65:      }
66:
67:      return 0;
68:  }


(1)Dog (2)Cat: 1
Mammal constructor...
Dog constructor...
(1)Dog (2)Cat: 2
Mammal constructor...
Cat constructor...
(1)Dog (2)Cat: 1
Mammal constructor...
Dog constructor...

Woof!
Uh oh, not a cat!
Mammal destructor...

Meow
rrrrrrrrrrr
Mammal destructor...

Woof!
Uh oh, not a cat!
Mammal destructor...
					

On lines 38–48, the user is asked to choose to add either a Cat or a Dog object to the array of Mammal pointers. Line 52 walks through the array and, on line 54, each object's virtual speak() method is called. These methods respond polymorphically: Cats meow, and dogs say woof!


On line 60, I want the Cat objects to purr, but I don't want to call that method on Dog objects. I used the dynamic_cast operator on line 57 to ensure that the object I am calling purr() on is a Cat. If it is, the pointer will not be null and will pass the test on line 59.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.109.4