MFC Programmer's SourceBook : Thinking in C++
Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

Syntax specifics

This section looks at the details of how the two forms of RTTI work, and how they differ.

typeid( ) with built-in types

For consistency, the typeid( ) operator works with built-in types. So the following expressions are true:

typeid(47) == typeid(int)
typeid(0) == typeid(int)
int i;
typeid(i) == typeid(int)
typeid(&i) == typeid(int*) 

Producing the proper type name

typeid( ) must work properly in all situations. For example, the following class contains a nested class:

//: C24:Rnest.cpp
// Nesting and RTTI
#include <iostream>
#include <typeinfo>
using namespace std;

class One {
  class Nested {};
  Nested* n;
public:
  One() : n(new Nested) {}
  ~One() { delete n; }
  Nested* nested() { return n; }
};

int main() {
  One o;
  cout << typeid(*o.nested()).name() << endl;
} ///:~ 

The typeinfo::name( ) member function will still produce the proper class name; the result is One::Nested.

Nonpolymorphic types

Although typeid( ) works with nonpolymorphic types (those that don’t have a virtual function in the base class), the information you get this way is dubious. For the following class hierarchy,

class X {
 int i; 
public:
  // ...
};
class Y : public X { 
  int j;
public:
  // ...
};

If you create an object of the derived type and upcast it,

X* xp = new Y;

The typeid( ) operator will produce results, but not the ones you might expect. Because there’s no polymorphism, the static type information is used:

typeid(*xp) == typeid(X)
typeid(*xp) != typeid(Y) 

RTTI is intended for use only with polymorphic classes.

Casting to intermediate levels

dynamic_cast can detect both exact types and, in an inheritance hierarchy with multiple levels, intermediate types. For example,

class D1 { 
public:
  virtual void func() {} 
  virtual ~D1() {}
};
class D2 { 
public:
  virtual void bar() {} 
};
class MI : public D1, public D2 {};
class Mi2 : public MI {};

D2* d2 = new Mi2;
Mi2* mi2 = dynamic_cast<Mi2*>(d2);
MI* mi = dynamic_cast<MI*>(d2); 
This has the extra complication of multiple inheritance. If you create an mi2 and upcast it to the root (in this case, one of the two possible roots is chosen), then 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:

D1* d1 = dynamic_cast<D1*>(d2);

This is successful because D2 is actually pointing to an mi2 object, which contains a subobject of type d1.

Casting to intermediate levels brings up an interesting difference between dynamic_cast and typeid( ). typeid( ) always produces a reference to a typeinfo object that describes the exact type of the object. Thus it doesn’t give you intermediate-level information. In the following expression (which is true), typeid( ) doesn’t see d2 as a pointer to the derived type, like dynamic_cast does:

typeid(d2) != typeid(Mi2*)

The type of D2 is simply the exact type of the pointer:

typeid(d2) == typeid(D2*)

void pointers

Run-time type identification doesn’t work with void pointers:

//: C24: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.”

Using RTTI with templates

Templates generate many different class names, and sometimes you’d like to print out information about what class you’re in. RTTI provides a convenient way to do this. The following example revisits the code in Chapter 12 to print out the order of constructor and destructor calls without using a preprocessor macro:

//: C24:Inhorder.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; 
} ///:~ 

The <typeinfo> header must be included to call any member functions for the typeinfo object returned by typeid( ). The template uses a constant int to differentiate one class from another, but class arguments will work as well. Inside both the constructor and destructor, RTTI information is used to produce 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.

This technique is often useful in situations when you’re trying to understand how the language works.

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru