};
class B2 {
public:
virtual ~B2() {}
};
class MI : public B1, public B2 {};
class Mi2 : public MI {};
int main() {
B2* b2 = new Mi2;
Mi2* mi2 = dynamic_cast<Mi2*>(b2);
MI* mi = dynamic_cast<MI*>(b2);
B1* b1 = dynamic_cast<B1*>(b2);
assert(typeid(b2) != typeid(Mi2*));
assert(typeid(b2) == typeid(B2*));
delete b2;
} ///:~
This example has the extra complication of multiple inheritance (more on this later in this chapter). If you create an Mi2 and upcast it to the root (in this case, one of the two possible roots is chosen), the dynamic_cast back to either of the derived levels MI or Mi2 is successful.
You can even cast from one root to the other:
B1* b1 = dynamic_cast<B1*>(b2);
This is successful because B2 is actually pointing to an Mi2 object, which contains a subobject of type B1.
Casting to intermediate levels brings up an interesting difference between dynamic_cast and typeid. The typeid operator always produces a reference to a static typeinfo object that describes the dynamic type of the object. Thus, it doesn’t give you intermediate-level information. In the following expression (which is true), typeid doesn’t see b2 as a pointer to the derived type, like dynamic_cast does:
typeid(b2) != typeid(Mi2*)
The type of b2 is simply the exact type of the pointer:
typeid(b2) == typeid(B2*)
void pointers
RTTI only works for complete types, meaning that all class information must be available when typeid is used. In particular, it doesn’t work with void pointers:
//: C08:VoidRTTI.cpp
// RTTI & void pointers
//!#include <iostream>
#include <typeinfo>
using namespace std;
class Stimpy {
public:
virtual void happy() {}
virtual void joy() {}
virtual ~Stimpy() {}
};
int main() {
void* v = new Stimpy;
// Error:
//! Stimpy* s = dynamic_cast<Stimpy*>(v);
// Error:
//! cout << typeid(*v).name() << endl;
} ///:~
A void* truly means "no type information at all."[106]
Using RTTI with templates
Class templates work well with RTTI, since all they do is generate classes. As usual, RTTI provides a convenient way to obtain the name of the class you’re in. The following example prints the order of constructor and destructor calls:
//: C08:ConstructorOrder.cpp
// Order of constructor calls
#include <iostream>
#include <typeinfo>
using namespace std;
template<int id> class Announce {
public:
Announce() {
cout << typeid(*this).name()
<< " constructor" << endl;
}
~Announce() {
cout << typeid(*this).name()
<< " destructor" << endl;
}
};
class X : public Announce<0> {
Announce<1> m1;
Announce<2> m2;
public:
X() { cout << "X::X()" << endl; }
~X() { cout << "X::~X()" << endl; }
};
int main() { X x; } ///:~
This template uses a constant int to differentiate one class from another, but type arguments would work as well. Inside both the constructor and destructor, RTTI information produces the name of the class to print. The class X uses both inheritance and composition to create a class that has an interesting order of constructor and destructor calls. The output is:
Announce<0> constructor
Announce<1> constructor
Announce<2> constructor
X::X()
X::~X()
Announce<2> destructor
Announce<1> destructor
Announce<0> destructor
Multiple inheritance
Of course, the RTTI mechanisms must work properly with all the complexities of multiple inheritance, including virtual base classes (discussed in depth in the next chapter—you may want to come back to this after reading Chapter 9):
//: C08:RTTIandMultipleInheritance.cpp
#include <iostream>
#include <typeinfo>
using namespace std;
class BB {
public:
virtual void f() {}
virtual ~BB() {}
};
class B1 : virtual public BB {};
class B2 : virtual public BB {};
class MI : public B1, public B2 {};
int main() {
BB* bbp = new MI; // Upcast
// Proper name detection:
cout << typeid(*bbp).name() << endl;
// Dynamic_cast works properly:
MI* mip = dynamic_cast<MI*>(bbp);
// Can't force old-style cast:
//! MI* mip2 = (MI*)bbp; // Compile error
} ///:~
The typeid( ) operation properly detects the name of the actual object, even through the virtual base class pointer. The dynamic_cast also works correctly. But the compiler won’t even allow you to try to force a cast the old way:
MI* mip = (MI*)bbp; // Compile-time error
The compiler knows this is never the right thing to do, so it requires that you use a dynamic_cast.
Sensible uses for RTTI
Because it allows you to discover type information from an anonymous polymorphic pointer, RTTI is ripe for misuse by the novice because RTTI may make sense before virtual functions do. For many people coming from a procedural background, it’s difficult not to organize programs into sets of switch statements. They could accomplish this with RTTI and thus lose the important value of polymorphism in code development and maintenance. The intent of C++ is that you use virtual functions throughout your code and that you only use RTTI when you must.
However, using virtual functions as they are intended requires that you have control of the base-class definition because at some point in the extension of your program you may discover the base class doesn’t include the virtual function you need. If the base class comes from a library or is otherwise controlled by someone else, a solution to the problem is RTTI: you can derive a new type and add your extra member function. Elsewhere in the code you can detect your particular type and call that member function. This doesn’t destroy the polymorphism and extensibility of the program, because adding a new type will not require you to hunt for switch statements. However, when you add new code in the main body that requires your new feature, you’ll have to detect your particular type.
106
A dynamic_cast<void*> always gives the address of the full objectвЂ"not a subobject. This will be explained more fully in the next chapter.