Professional C__ - Marc Gregoire [477]
Bear myBear;
Fish myFish;
Animal& animalRef = myFish;
cout << myBear.eats(animalRef) << endl; // BUG! No method Bear::eats(Animal&)
Because the compiler needs to know which overloaded version of the eats() method is going to be called at compile time, this solution is not truly polymorphic. It would not work, for example, if you were iterating over an array of Animal references and passing each one to a call to eats().
Attempt #3: Double Dispatch
The double dispatch technique is a truly polymorphic solution to the multiple type problem. In C++, polymorphism is achieved by overriding methods in subclasses. At run time, methods are called based on the actual type of the object. The preceding single polymorphic attempt didn’t work because it attempted to use polymorphism to determine which overloaded version of a method to call instead of using it to determine on which class to call the method.
To begin, focus on a single subclass, perhaps the Bear class. The class needs a method with the following declaration:
virtual bool eats(const Animal& inPrey) const;
The key to double dispatch is to determine the result based on a method call on the argument. Suppose that the Animal class had a method called eatenBy(), which took an Animal reference as a parameter. This method would return true if the current Animal gets eaten by the one passed in. With such a method, the definition of eats() becomes very simple:
bool Bear::eats(const Animal& inPrey) const
{
return inPrey.eatenBy(*this);
}
At first, it looks like this solution adds another layer of method calls to the single polymorphic method. After all, each subclass will still have to implement a version of eatenBy() for every subclass of Animal. However, there is a key difference. Polymorphism is occurring twice! When you call the eats() method on an Animal, polymorphism determines whether you are calling Bear::eats(), Fish::eats(), or one of the others. When you call eatenBy(), polymorphism again determines which class’s version of the method to call. It calls eatenBy() on the run-time type of the inPrey object. Note that the run-time type of *this is always the same as the compile-time type so that the compiler can call the correct overloaded version of eatenBy() for the argument (in this case Bear).
Following are the class definitions for the Animal hierarchy using double dispatch. Note that forward class declarations are necessary because the base class uses references to the subclasses.
// forward declarations
class Fish;
class Bear;
class Dinosaur;
class Animal
{
public:
virtual bool eats(const Animal& inPrey) const = 0;
virtual bool eatenBy(const Bear& inBear) const = 0;
virtual bool eatenBy(const Fish& inFish) const = 0;
virtual bool eatenBy(const Dinosaur& inDinosaur) const = 0;
};
class Bear : public Animal
{
public:
virtual bool eats(const Animal& inPrey) const;
virtual bool eatenBy(const Bear& inBear) const;
virtual bool eatenBy(const Fish& inFish) const;
virtual bool eatenBy(const Dinosaur& inDinosaur) const;
};
class Fish : public Animal
{
public:
virtual bool eats(const Animal& inPrey) const;
virtual bool eatenBy(const Bear& inBear) const;
virtual bool eatenBy(const Fish& inFish) const;
virtual bool eatenBy(const Dinosaur& inDinosaur) const;
};
class Dinosaur : public Animal
{
public:
virtual bool eats(const Animal& inPrey) const;
virtual bool eatenBy(const Bear& inBear) const;
virtual bool eatenBy(const Fish& inFish) const;
virtual bool eatenBy(const Dinosaur& inDinosaur) const;
};
Code snippet from DoubleDispatch\DoubleDispatch.cpp
The implementations follow. Note that each Animal subclass implements the eats() method in the same way, but it cannot be factored up into the parent class. The reason is that if you attempt to do