Runtime Type Information in C++

Recently, type_info, a meta-class that provides a limited amount of somewhat unreliable runtime type information, was added to the standard C++ library. The type_info class includes member functions for discovering the name of the type (name and raw_name), for comparing types (== and !=), and for determining the order of types in collating sequences (collating sequences are used by objects representing locales):

class type_info
{
public:
   virtual ~type_info();
   int operator==(const type_info& rhs) const;
   int operator!=(const type_info& rhs) const;
   int before(const type_info& rhs) const;
   const char* name() const;
   const char* raw_name() const;
private:
   ...
};

The standard C++ library also provides a global operator named typeid() that expects any expression as input and returns a constant reference to a type_info object representing the expression's type:

const type_info& typeid(exp);

For example, assume we define the C++ version of our Note class:

class Note
{
public:
   Note()
   {
      frequency = 60;
      duration = 300;
   }
   virtual void play()
   {
      cout << "playing a generic note" << endl;
   }
private:
   int frequency; // in Hz
   int duration;  // in millisecs
};

As in our Java version, HornNote and ViolinNote are defined as subclasses:

class HornNote: public Note
{
public:
   void play()
   {
      cout << "playing a horn note" << endl;
   }
};

class ViolinNote: public Note
{
public:
   void play()
   {
      cout << "playing a violin note" << endl;
   }
};

For testing purposes, we introduce a global function that displays the name of the type of note pointed at by its parameter:

void displayType(Note* note)
{
   const type_info& tp = typeid(*note);
   cout << "type = " << tp.name() << endl;
}

Notice that the value returned by typeid() must be stored in a constant reference variable.

Our test driver, main(), passes the same note pointer to displayType() three times. However, each time the pointer points to a different type of Note object:

Note* note = new Note();
displayType(note);

note = new HornNote();
displayType(note);

note = new ViolinNote();
displayType(note);

To get main() and displayType() to compile, we need the following include directives:

#include <typeinfo>
#include <iostream>
using namespace std;

Some compilers generate the necessary type information by default. To save space, other compilers, such as VC++, require programmers to set special compiler options before the necessary type information will be generated.[1]

It should also be mentioned that distinct type information for a subclass is generated by the typeid() operator only if the base class is polymorphic (i.e., contains at least one virtual function). Otherwise, the typeid() operator simply produces the type_info object of the base class.

Unfortunately, the output produced by the test program is compiler-dependent. The output produced by DJGPP, a GNU compiler for Windows, prefaces the type names by their lengths:

type = 4Note
type = 8HornNote
type = 10ViolinNote

The output produced by VC++ prefaces type names with the string "class":

type = class Note
type = class HornNote
type = class ViolinNote

The typeid() operator can also convert a type expression into its associated type_info object.

const type_info& typeid(type-exp);

We can compare this object to the type_info object of an expression using the == operator. This could be used to perform safe downcasts. For example, assume we add a special honk() function to our HornNote class:

class HornNote: public Note
{
public:
   void honk()
   {
      cout << "HONK, HONK!\n";
   }
   // etc.
};

Assume a test program uses a generic Note pointer to point at a HornNote object:

Note* note = 0;
// and later:
note = new HornNote();

Now assume we want to call the honk() function. An explicit downcast will be required, but we want to be sure note points to a HornNote before proceeding. Here's one way to do this:

if (typeid(*note) == typeid(HornNote))
   ((HornNote*)note)->honk();
else
   cerr << "Error: unexpected type\n";

Of course this is exactly what the dynamic_cast<> operator does:

HornNote* hornNote = dynamic_cast<HornNote*>(note);
if (hornNote)
   hornNote->honk();
else
   cerr << "Error: unexpected type\n";

 



[1] In VC++, select the C/C++ tab in the dialog that appears when you select Settings from the Project menu. Pick "C++ Language" in the Category combo box. Check the "Enable Runtime Type Information (RTTI)" box