Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

Multiple dispatching

When dealing with multiple interacting types, a program can get particularly messy. For example, consider a system that parses and executes mathematical expressions. You want to be able to say Number + Number, Number * Number, and so on, where Number is the base class for a family of numerical objects. But when you say a + b, and you don t know the exact type of either a or b, how can you get them to interact properly?

The answer starts with something you probably don t think about: C++ performs only single dispatching. That is, if you are performing an operation on more than one object whose type is unknown, C++ can invoke the dynamic binding mechanism on only one of those types. This doesn t solve the problem described here, so you end up detecting some types manually and effectively producing your own dynamic binding behavior.

The solution is called multiple dispatching (described in GoF in the context of the Visitor pattern, shown in the next section). Here, there will be only two dispatches, which is referred to as double dispatching. Remember that polymorphism can occur only via virtual function calls, so if you want multiple dispatching to occur, there must be a virtual function call to determine each unknown type. Thus, if you are working with two different type hierarchies that are interacting, you ll need a virtual call in each hierarchy. Generally, you ll set up a configuration such that a single member function call generates more than one virtual member function call and thus determines more than one type in the process: you ll need a virtual function call for each dispatch. The virtual functions in the following example are called compete( ) and eval( ) and are both members of the same type (this is not a requirement for multiple dispatching):[146]

//: C10:PaperScissorsRock.cpp
// Demonstration of multiple dispatching.
#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
#include <ctime>
#include <cstdlib>
#include "../purge.h"
using namespace std;
 
class Paper;
class Scissors;
class Rock;
 
enum Outcome { WIN, LOSE, DRAW };
 
ostream& operator<<(ostream& os, const Outcome out) {
switch(out) {
default:
case WIN: return os << "win";
case LOSE: return os << "lose";
case DRAW: return os << "draw";
}
}
 
class Item {
public:
virtual Outcome compete(const Item*) = 0;
virtual Outcome eval(const Paper*) const = 0;
virtual Outcome eval(const Scissors*) const= 0;
virtual Outcome eval(const Rock*) const = 0;
virtual ostream& print(ostream& os) const = 0;
virtual ~Item() {}
friend ostream& operator<<(ostream& os, const Item* it) {
return it->print(os);
}
};
 
class Paper : public Item {
public:
Outcome compete(const Item* it) { return it->eval(this);}
Outcome eval(const Paper*) const { return DRAW; }
Outcome eval(const Scissors*) const { return WIN; }
Outcome eval(const Rock*) const { return LOSE; }
ostream& print(ostream& os) const {
return os << "Paper ";
}
};
 
class Scissors : public Item {
public:
Outcome compete(const Item* it) { return it->eval(this);}
Outcome eval(const Paper*) const { return LOSE; }
Outcome eval(const Scissors*) const { return DRAW; }
Outcome eval(const Rock*) const { return WIN; }
ostream& print(ostream& os) const {
return os << "Scissors";
}
};
 
class Rock : public Item {
public:
Outcome compete(const Item* it) { return it->eval(this);}
Outcome eval(const Paper*) const { return WIN; }
Outcome eval(const Scissors*) const { return LOSE; }
Outcome eval(const Rock*) const { return DRAW; }
ostream& print(ostream& os) const {
return os << "Rock ";
}
};
 
struct ItemGen {
Item* operator()() {
switch(rand() % 3) {
default:
case 0: return new Scissors;
case 1: return new Paper;
case 2: return new Rock;
}
}
};
 
struct Compete {
Outcome operator()(Item* a, Item* b) {
cout << a << "\t" << b << "\t";
return a->compete(b);
}
};
 
int main() {
srand(time(0)); // Seed the random number generator
const int sz = 20;
vector<Item*> v(sz*2);
generate(v.begin(), v.end(), ItemGen());
transform(v.begin(), v.begin() + sz,
v.begin() + sz,
ostream_iterator<Outcome>(cout, "\n"),
Compete());
purge(v);
} ///:~
 

Outcome categorizes the different possible results of a compete( ), and the operator<< simplifies the process of displaying a particular Outcome.

Item is the base class for the types that will be multiply-dispatched. Compete::operator( ) takes two Item* (the exact type of both are unknown) and begins the double-dispatching process by calling the virtual Item::compete( ) function. The virtual mechanism determines the type a, so it wakes up inside the compete( ) function of a s concrete type. The compete( ) function performs the second dispatch by calling eval( ) on the remaining type. Passing itself (this) as an argument to eval( ) produces a call to the overloaded eval( ) function, thus preserving the type information of the first dispatch. When the second dispatch is completed, you know the exact types of both Item objects.

In main( ), the STL algorithm generate( ) populates the vector v, then transform( ) applies Compete::operator( ) to the two ranges. This version of transform( ) takes the start and end point of the first range (containing the left-hand Items used in the double dispatch); the starting point of the second range, which holds the right-hand Items; the destination iterator, which in this case is standard output; and the function object (a temporary of type Compete) to call for each object.

It requires a lot of ceremony to set up multiple dispatching, but keep in mind that the benefit is the syntactic elegance achieved when making the call instead of writing awkward code to determine the type of one or more objects during a call, you simply say: You two! I don t care what types you are, interact properly with each other! Make sure this kind of elegance is important to you before embarking on multiple dispatching, however.

Note that multiple dispatching is, in effect, performing a table lookup. Here, the lookup is performed using virtual functions, but you could instead perform a literal table lookup. With more than a few dispatches (and if you are prone to making additions and changes), a table lookup may be a better solution to the problem.

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire