Online Book Reader

Home Category

Professional C__ - Marc Gregoire [476]

By Root 1339 0
types of two or more objects. In practice, double dispatch, which chooses a behavior based on the run-time types of exactly two objects, is usually sufficient.

Attempt #1: Brute Force

The most straightforward way to implement a method whose behavior depends on the run-time types of two different objects is to take the perspective of one of the objects and use a series of if/else constructs to check the type of the other. For example, you could implement a method called eats() on each Animal subclass that takes the other animal as an argument. The method would be declared pure virtual in the base class as follows:

class Animal

{

public:

virtual bool eats(const Animal& inPrey) const = 0;

};

Code snippet from DoubleDispatch\DoubleDispatchBruteForce.cpp

Each subclass would implement the eats() method and return the appropriate value based on the type of the argument. The implementation of eats() for several subclasses follows. Note that the Dinosaur subclass avoids the series of if/else constructs because (according to the authors) dinosaurs eat anything.

bool Bear::eats(const Animal& inPrey) const

{

if (typeid(inPrey) == typeid(Bear&)) {

return false;

} else if (typeid(inPrey) == typeid(Fish&)) {

return true;

} else if (typeid(inPrey) == typeid(Dinosaur&)) {

return false;

}

return false;

}

bool Fish::eats(const Animal& inPrey) const

{

if (typeid(inPrey) == typeid(Bear&)) {

return false;

} else if (typeid(inPrey) == typeid(Fish&)) {

return true;

} else if (typeid(inPrey) == typeid(Dinosaur&)) {

return false;

}

return false;

}

bool Dinosaur::eats(const Animal& inPrey) const

{

return true;

}

Code snippet from DoubleDispatch\DoubleDispatchBruteForce.cpp

This brute force approach works, and it’s probably the most straightforward technique for a small number of classes. However, there are several reasons why you might want to avoid such an approach:

OOP purists often frown upon explicitly querying the type of an object because it implies a design that is lacking in proper object-oriented structure.

As the number of types grows, such code can grow messy and repetitive.

This approach does not force subclasses to consider new types. For example, if you added a Donkey subclass, the Bear class would continue to compile, but would return false when told to eat a Donkey, even though everybody knows that bears eat donkeys.

Attempt #2: Single Polymorphism with Overloading

You could attempt to use polymorphism with overloading to circumvent all of the cascading if/else constructs. Instead of giving each class a single eats() method that takes an Animal reference, why not overload the method for each Animal subclass? The base class definition would look like this:

class Animal

{

public:

virtual bool eats(const Bear& inPrey) const = 0;

virtual bool eats(const Fish& inPrey) const = 0;

virtual bool eats(const Dinosaur& inPrey) const = 0;

};

Because the methods are pure virtual in the superclass, each subclass would be forced to implement the behavior for every other type of Animal. For example, the Bear class would contain the following methods:

class Bear : public Animal

{

public:

virtual bool eats(const Bear& inPrey) const { return false; }

virtual bool eats(const Fish& inPrey) const { return true; }

virtual bool eats(const Dinosaur& inPrey) const { return false; }

};

This approach initially appears to work, but it really solves only half of the problem. In order to call the proper eats() method on an Animal, the compiler needs to know the compile-time type of the animal being eaten. A call such as the following will be successful because the compile-time types of both the eater and the eaten animals are known:

Bear myBear;

Fish myFish;

cout << myBear.eats(myFish) << endl;

The missing piece is that the solution is only polymorphic in one direction. You could access myBear in the context of an Animal and the correct method would be called:

Bear myBear;

Fish myFish;

Animal& animalRef = myBear;

cout << animalRef.eats(myFish) << endl;

However, the reverse is not true. If

Return Main Page Previous Page Next Page

®Online Book Reader