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,
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.
Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru