Professional C__ - Marc Gregoire [121]
class Dog
{
public:
virtual void bark() { cout << "Woof!" << endl; }
virtual void eat() { cout << "The dog has eaten." << endl; }
};
class Bird
{
public:
virtual void chirp() { cout << "Chirp!" << endl; }
virtual void eat() { cout << "The bird has eaten." << endl; }
};
class DogBird : public Dog, public Bird
{
};
int main()
{
DogBird myConfusedAnimal;
myConfusedAnimal.eat(); // BUG! Ambiguous call to method eat()
return 0;
}
Code snippet from DogBird\DogBird.cpp
The solution to the ambiguity is to either explicitly upcast the object, essentially hiding the undesired version of the method from the compiler, or to use a disambiguation syntax. For example, the following code shows two ways to invoke the Dog version of eat():
static_cast myConfusedAnimal.Dog::eat(); // Calls Dog::eat() Code snippet from DogBird\DogBird.cpp Methods of the subclass itself can also explicitly disambiguate between different methods of the same name by using the same syntax used to access parent methods, the :: operator. For example, the DogBird class could prevent ambiguity errors in other code by defining its own eat() method. Inside this method, it would determine which parent version to call. void DogBird::eat() { Dog::eat(); // Explicitly call Dog's version of eat() } Code snippet from DogBird\DogBird.cpp Another way to provoke ambiguity is to inherit from the same class twice. For example, if the Bird class inherited from Dog for some reason, the code for DogBird would not compile because Dog becomes an ambiguous base class. class Dog {}; class Bird : public Dog {}; class DogBird : public Bird, public Dog {}; // BUG! Dog is an ambiguous base class. Most occurrences of ambiguous base classes are either contrived “what-if” examples, as in the preceding, or arise from untidy class hierarchies. Figure 8-8 shows a class diagram for the preceding example, indicating the ambiguity. FIGURE 8-8 Ambiguity can also occur with data members. If Dog and Bird both had a data member with the same name, an ambiguity error would occur when client code attempted to access that member. Ambiguous Base Classes A more likely scenario is that multiple parents themselves have common parents. For example, perhaps both Bird and Dog are subclasses of an Animal class, as shown in Figure 8-9. FIGURE 8-9 This type of class hierarchy is permitted in C++, though name ambiguity can still occur. For example, if the Animal class has a public method called sleep(), that method could not be called on a DogBird object because the compiler would not know whether to call the version inherited by Dog or by Bird. The best way to use these “diamond-shaped” class hierarchies is to make the topmost class an abstract base class with all methods declared as pure virtual. Since the class only declares methods without providing definitions, there are no methods in the base class to call and thus there are no ambiguities at that level. The following example implements a diamond-shaped class hierarchy with a pure virtual eat() method that must be defined by each subclass. The DogBird class still needs to be explicit about which parent’s eat() method it uses, but any ambiguity would be caused by Dog and Bird having the same method, not because they inherit from the same class. class Animal { public: virtual void eat() = 0; }; class Dog : public Animal { public: virtual void bark() { cout << "Woof!" << endl; } virtual void eat() { cout << "The dog has eaten." << endl; } }; class Bird : public Animal { public: virtual void chirp() { cout << "Chirp!" << endl; } virtual void eat() { cout << "The bird has eaten." << endl; } }; class DogBird : public Dog, public Bird { public: virtual void eat()