// Comparator for type_info pointers
struct TInfoLess {
bool operator()(const type_info* t1, const type_info* t2)
const {
return t1->before(*t2);
}
};
typedef map<const type_info*, vector<Trash*>, TInfoLess>
TrashMap;
// Sums up the value of the Trash in a bin:
void sumValue(const TrashMap::value_type& p, ostream& os) {
vector<Trash*>::const_iterator tally = p.second.begin();
float val = 0;
while(tally != p.second.end()) {
val += (*tally)->weight() * (*tally)->value();
os << "weight of "
<< p.first->name() // type_info::name()
<< " = " << (*tally)->weight() << endl;
tally++;
}
os << "Total value = " << val << endl;
}
int main() {
srand(time(0)); // Seed random number generator
TrashMap bin;
// Fill up the Trash bin:
for(int i = 0; i < 30; i++) {
Trash* tp;
switch(rand() % 3) {
case 0 :
tp = new Aluminum((rand() % 1000)/10.0);
break;
case 1 :
tp = new Paper((rand() % 1000)/10.0);
break;
case 2 :
tp = new Glass((rand() % 1000)/10.0);
break;
}
bin[&typeid(*tp)].push_back(tp);
}
// Print sorted results
for(TrashMap::iterator p = bin.begin();
p != bin.end(); ++p) {
sumValue(*p, cout);
purge(p->second);
}
} ///:~
We’ve modified sumValue( ) to call type_info::name( ) directly, since the type_info object is now available there as the first member of the TrashMap::value_type pair. This avoids the extra call to typeid to get the name of the type of Trash being processed that was necessary in the previous version of this program.
Mechanism and overhead of RTTI
Typically, RTTI is implemented by placing an additional pointer in a class’s virtual function table. This pointer points to the type_info structure for that particular type. The effect of a typeid( ) expression is quite simple: the virtual function table pointer fetches the type_info pointer, and a reference to the resulting type_info structure is produced. Since this is just a two-pointer dereference operation, it is a constant time operation.
For a dynamic_cast<destination*>(source_pointer), most cases are quite straightforward: source_pointer’s RTTI information is retrieved, and RTTI information for the type destination* is fetched. A library routine then determines whether source_pointer’s type is of type destination* or a base class of destination*. The pointer it returns may be adjusted because of multiple inheritance if the base type isn’t the first base of the derived class. The situation is (of course) more complicated with multiple inheritance in which a base type may appear more than once in an inheritance hierarchy and virtual base classes are used.
Because the library routine used for dynamic_cast must check through a list of base classes, the overhead for dynamic_cast may be higher than typeid( ) (but of course you get different information, which may be essential to your solution), and it may take more time to discover a base class than a derived class. In addition, dynamic_cast allows you to compare any type to any other type; you aren’t restricted to comparing types within the same hierarchy. This adds extra overhead to the library routine used by dynamic_cast.
Summary
Although normally you upcast a pointer to a base class and then use the generic interface of that base class (via virtual functions), occasionally you get into a corner where things can be more effective if you know the dynamic type of the object pointed to by a base pointer, and that’s what RTTI provides. The most common misuse may come from the programmer who doesn’t understand virtual functions and uses RTTI to do type-check coding instead. The philosophy of C++ seems to be to provide you with powerful tools and guard for type violations and integrity, but if you want to deliberately misuse or get around a language feature, there’s nothing to stop you. Sometimes a slight burn is the fastest way to gain experience.
Exercises
1. Modify C16:AutoCounter.h in Volume 1 of this series so that it becomes a useful debugging tool. It will be used as a nested member of each class that you are interested in tracing. Turn AutoCounter into a template that takes the class name of the surrounding class as the template argument, and in all the error messages use RTTI to print out the name of the class.
58. Use RTTI to assist in program debugging by printing out the exact name of a template using typeid( ). Instantiate the template for various types and see what the results are.
59. Modify the Instrument hierarchy from Chapter 14 of Volume 1 by first copying Wind5.cpp to a new location. Now add a virtual ClearSpitValve( ) function to the Wind class, and redefine it for all the classes inherited from Wind. Instantiate a TStash to hold Instrument pointers, and fill it with various types of Instrument objects created using the new operator. Now use RTTI to move through the container looking for objects in class Wind, or derived from Wind. Call the ClearSpitValve( ) function for these objects. Notice that it would unpleasantly confuse the Instrument base class if it contained a ClearSpitValve( ) function.
9: Multiple inheritance
The basic concept of multiple inheritance (MI) sounds simple enough: you create a new type by inheriting from more than one base class. The syntax is exactly what you’d expect, and as long as the inheritance diagrams are simple, MI can be simple as well.
Or maybe not! MI can introduce a number of ambiguities and strange situations, which are covered in this chapter. But first, it will be helpful to get a little perspective on the subject.
Perspective
Before C++, the most successful object-oriented language was Smalltalk. Smalltalk was created from the ground up as an object-oriented language. It is often referred to as pure, whereas C++ is called a hybrid language because it supports multiple programming paradigms, not just the object-oriented paradigm. One of the design decisions made with Smalltalk was that all classes would be derived in a single hierarchy, rooted in a single base class (called Object—this is the model for the object-based hierarchy). You cannot create a new class in Smalltalk without deriving it from an existing class, which is why it takes a certain amount of time to become productive in Smalltalk: you must learn the class library before you can start making new classes. The Smalltalk class hierarchy is therefore a single monolithic tree.