What
is RTTI?
But
what if you have a special programming problem that’s easiest to solve if
you know the exact type of a generic pointer?
For example, suppose you want to allow your users to highlight all the shapes
of any particular type by turning them purple. This way, they can find all the
triangles on the screen by highlighting them. Your natural first approach may
be to try a virtual function like
TurnColorIfYouAreA( ),
which allows enumerated arguments of some type
color
and of
Shape::Circle,
Shape::Square,
or
Shape::Triangle. To
solve this sort of problem, most class library designers put virtual functions
in the base class to return type information about the specific object at
run-time. You may have seen library member functions with names like
isA( )
and
typeOf( ).
These are vendor-defined
RTTI functions. Using these functions, as you go through the list you can say,
“If you’re a triangle, turn purple.”
When
exception handling
was added to C++, the implementation required that some run-time type
information be put into the virtual function tables. This meant that with a
small language extension the programmer could also get the run-time type
information about an object. All library vendors were adding their own RTTI anyway,
so it was included in the language.
RTTI,
like exceptions, depends on type information residing in the virtual function
table. If you try to use RTTI on a class that has no virtual functions,
you’ll get unexpected results.
Two
syntaxes for RTTI
There
are two different ways to use RTTI. The first acts like
sizeof( )
because it looks like a function, but it’s actually implemented by the
compiler.
typeid( )
takes
an argument that’s an object, a reference, or a pointer and returns a
reference to a global
const
object of type
typeinfo.
These can be compared to each other with the
operator==
and
operator!=,
and you can also ask for the
name( )
of the type, which returns a string representation of the type name. Note that
if you hand
typeid( )
a
Shape*,
it will say that the type is
Shape*,
so if you want to know the exact type it is pointing to, you must dereference
the pointer. For example, if
s
is a
Shape*, cout
<< typeid(*s).name() << endl;
will
print out the type of the object
s
points to.
You
can also ask a
typeinfo
object if it precedes another
typeinfo
object in the implementation-defined “collation sequence,” using
before(typeinfo&),
which returns true or false. When you say,
if(typeid(me).before(typeid(you)))
// ...
you’re
asking if
me
occurs before
you
in the collation sequence.
The
second syntax for RTTI is called a “type-safe downcast.”
The reason for the term “downcast” is (again) the historical
arrangement of the class hierarchy diagram. If casting a
Circle*
to a
Shape*
is an upcast, then casting a
Shape*
to a
Circle*
is a downcast. However, you know a
Circle*
is also a
Shape*,and
the compiler freely allows an upcast assignment, but you
don’t
know that a
Shape*
is necessarily a
Circle*,
so the compiler doesn’t allow you to perform a downcast assignment
without using an explicit cast. You can of course force your way through using
ordinary C-style casts or a C++
static_cast
(described at the end of this chapter), which says, “I hope this is
actually a
Circle*,
and I’m going to pretend it is.” Without some explicit knowledge
that it
is
in fact a
Circle,
this is a totally dangerous thing to do. A common approach in vendor-defined
RTTI is to create some function that attempts to assign (for this example) a
Shape*
to a
Circle*,
checking the type in the process. If this function returns the address, it was
successful; if it returns null, you didn’t have a
Circle*. The
C++ RTTI typesafe-downcast follows this “attempt-to-cast” function
form, but it uses (very logically) the template syntax to produce the special
function
dynamic_cast.
So the example becomes
Shape* sp = new Circle;
Circle* cp = dynamic_cast<Circle*>(sp);
if(cp) cout << "cast successful";
The
template argument for
dynamic_cast
is the type you want the function to produce, and this is the return value for
the function. The function argument is what you are trying to cast from.
Normally
you might be hunting for one type (triangles to turn purple, for instance), but
the following example fragment can be used if you want to count the number of
various shapes.
Circle* cp = dynamic_cast<Circle*>(sh);
Square* sp = dynamic_cast<Square*>(sh);
Triangle* tp = dynamic_cast<Triangle*>(sh);
Of
course this is contrived – you’d probably put a
static
data member in each type and increment it in the constructor. You would do
something like that
if
you had control of the source code for the class and could change it.
Here’s an example that counts shapes using both the
static
member approach and
dynamic_cast:
//: C24:Rtshapes.cpp
// Counting shapes
#include <iostream>
#include <ctime>
#include <typeinfo>
#include <vector>
#include "../purge.h"
using namespace std;
class Shape {
protected:
static int count;
public:
Shape() { count++; }
virtual ~Shape() { count--; }
virtual void draw() const = 0;
static int quantity() { return count; }
};
int Shape::count = 0;
class SRectangle : public Shape {
void operator=(SRectangle&); // Disallow
protected:
static int count;
public:
SRectangle() { count++; }
SRectangle(const SRectangle&) { count++;}
~SRectangle() { count--; }
void draw() const {
cout << "SRectangle::draw()" << endl;
}
static int quantity() { return count; }
};
int SRectangle::count = 0;
class SEllipse : public Shape {
void operator=(SEllipse&); // Disallow
protected:
static int count;
public:
SEllipse() { count++; }
SEllipse(const SEllipse&) { count++; }
~SEllipse() { count--; }
void draw() const {
cout << "SEllipse::draw()" << endl;
}
static int quantity() { return count; }
};
int SEllipse::count = 0;
class SCircle : public SEllipse {
void operator=(SCircle&); // Disallow
protected:
static int count;
public:
SCircle() { count++; }
SCircle(const SCircle&) { count++; }
~SCircle() { count--; }
void draw() const {
cout << "SCircle::draw()" << endl;
}
static int quantity() { return count; }
};
int SCircle::count = 0;
int main() {
vector<Shape*> shapes;
srand(time(0)); // Seed random number generator
const int mod = 12;
// Create a random quantity of each type:
for(int i = 0; i < rand() % mod; i++)
shapes.push_back(new SRectangle);
for(int j = 0; j < rand() % mod; j++)
shapes.push_back(new SEllipse);
for(int k = 0; k < rand() % mod; k++)
shapes.push_back(new SCircle);
int nCircles = 0;
int nEllipses = 0;
int nRects = 0;
int nShapes = 0;
for(int u = 0; u < shapes.size(); u++) {
shapes[u]->draw();
if(dynamic_cast<SCircle*>(shapes[u]))
nCircles++;
if(dynamic_cast<SEllipse*>(shapes[u]))
nEllipses++;
if(dynamic_cast<SRectangle*>(shapes[u]))
nRects++;
if(dynamic_cast<Shape*>(shapes[u]))
nShapes++;
}
cout << endl << endl
<< "Circles = " << nCircles << endl
<< "Ellipses = " << nEllipses << endl
<< "Rectangles = " << nRects << endl
<< "Shapes = " << nShapes << endl
<< endl
<< "SCircle::quantity() = "
<< SCircle::quantity() << endl
<< "SEllipse::quantity() = "
<< SEllipse::quantity() << endl
<< "SRectangle::quantity() = "
<< SRectangle::quantity() << endl
<< "Shape::quantity() = "
<< Shape::quantity() << endl;
purge(shapes);
Both
types work for this example, but the
static
member approach can be used only if you own the code and have installed the
static
members and functions (or if a vendor provides them for you). In addition, the
syntax for RTTI may then be different from one class to another.
Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru