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

Creating your own RTTI

If your compiler doesn’t yet support RTTI, you can build it into your class libraries quite easily. This makes sense because RTTI was added to the language after observing that virtually all class libraries had some form of it anyway (and it was relatively “free” after exception handling was added because exceptions require exact knowledge of type information).

Essentially, RTTI requires only a virtual function to identify the exact type of the class, and a function to take a pointer to the base type and cast it down to the more derived type; this function must produce a pointer to the more derived type. (You may also wish to handle references.) There are a number of approaches to implement your own RTTI, but all require a unique identifier for each class and a virtual function to produce type information. The following uses a static member function called dynacast( ) that calls a type information function dynamic_type( ). Both functions must be defined for each new derivation:

//: C24:Selfrtti.cpp
// Your own RTTI system
#include <iostream>
#include <vector>
#include "../purge.h"
using namespace std;

class Security {
protected:
  enum { baseID = 1000 };
public:
  virtual int dynamic_type(int id) {
    if(id == baseID) return 1;
    return 0;
  }
};

class Stock : public Security {
protected:
  enum { typeID = baseID + 1 };
public:
  int dynamic_type(int id) {
    if(id == typeID) return 1;
    return Security::dynamic_type(id);
  }
  static Stock* dynacast(Security* s) {
    if(s->dynamic_type(typeID))
      return (Stock*)s;
    return 0;
  }
};

class Bond : public Security {
protected:
  enum { typeID = baseID + 2 };
public:
  int dynamic_type(int id) {
    if(id == typeID) return 1;
    return Security::dynamic_type(id);
  }
  static Bond* dynacast(Security* s) {
    if(s->dynamic_type(typeID))
      return (Bond*)s;
    return 0;
  }
};

class Commodity : public Security {
protected:
  enum { typeID = baseID + 3};
public:
  int dynamic_type(int id) {
    if(id == typeID) return 1;
    return Security::dynamic_type(id);
  }
  static Commodity* dynacast(Security* s) {
    if(s->dynamic_type(typeID))
      return (Commodity*)s;
    return 0;
  }
  void special() {
    cout << "special Commodity function\n";
  }
};

class Metal : public Commodity {
protected:
  enum { typeID = baseID + 4};
public:
  int dynamic_type(int id) {
    if(id == typeID) return 1;
    return Commodity::dynamic_type(id);
  }
  static Metal* dynacast(Security* s) {
    if(s->dynamic_type(typeID))
      return (Metal*)s;
    return 0;
  }
};

int main() {
  vector<Security*> portfolio;
  portfolio.push_back(new Metal);
  portfolio.push_back(new Commodity);
  portfolio.push_back(new Bond);
  portfolio.push_back(new Stock);
  vector<Security*>::iterator it = 
    portfolio.begin();
  while(it != portfolio.end()) {
    Commodity* cm = Commodity::dynacast(*it);
    if(cm) cm->special();
    else cout << "not a Commodity" << endl;
    it++;
  }
  cout << "cast from intermediate pointer:\n";
  Security* sp = new Metal;
  Commodity* cp = Commodity::dynacast(sp);
  if(cp) cout << "it's a Commodity\n";
  Metal* mp = Metal::dynacast(sp);
  if(mp) cout << "it's a Metal too!\n";
  purge(portfolio);
} ///:~ 

Each subclass must create its own typeID, redefine the virtual dynamic_type( ) function to return that typeID, and define a static member called dynacast( ), which takes the base pointer (or a pointer at any level in a deeper hierarchy – in that case, the pointer is simply upcast).

In the classes derived from Security, you can see that each defines its own typeID enumeration by adding to baseID. It’s essential that baseID be directly accessible in the derived class because the enum must be evaluated at compile-time, so the usual approach of reading private data with an inline function would fail. This is a good example of the need for the protected mechanism.

The enum baseID establishes a base identifier for all types derived from Security. That way, if an identifier clash ever occurs, you can change all the identifiers by changing the base value. (However, because this scheme doesn’t compare different inheritance trees, an identifier clash is unlikely). In all the classes, the class identifier number is protected, so it’s directly available to derived classes but not to the end user.

This example illustrates what built-in RTTI must cope with. Not only must you be able to determine the exact type, you must also be able to find out whether your exact type is derived from the type you’re looking for. For example, Metal is derived from Commodity, which has a function called special( ), so if you have a Metal object you can call special( ) for it. If dynamic_type( ) told you only the exact type of the object, you could ask it if a Metal were a Commodity, and it would say “no,” which is untrue. Therefore, the system must be set up so it will properly cast to intermediate types in a hierarchy as well as exact types.

The dynacast( ) function determines the type information by calling the virtual dynamic_type( ) function for the Security pointer it’s passed. This function takes an argument of the typeID for the class you’re trying to cast to. It’s a virtual function, so the function body is the one for the exact type of the object. Each dynamic_type( ) function first checks to see if the identifier it was passed is an exact match for its own type. If that isn’t true, it must check to see if it matches a base type; this is accomplished by making a call to the base class dynamic_type( ). Just like a recursive function call, each dynamic_type( ) checks against its own identifier. If it doesn’t find a match, it returns the result of calling the base class dynamic_type( ). When the root of the hierarchy is reached, zero is returned to indicate no match was found.

If dynamic_type( ) returns one (for “true”) the object pointed to is either the exact type you’re asking about or derived from that type, and dynacast( ) takes the Security pointer and casts it to the desired type. If the return value is false, dynacast( ) returns zero to indicate the cast was unsuccessful. In this way it works just like the C++ dynamic_cast operator.

The C++ dynamic_cast operator does one more thing the above scheme can’t do: It compares types from one inheritance hierarchy to another, completely separate inheritance hierarchy. This adds generality to the system for those unusual cases where you want to compare across hierarchies, but it also adds some complexity and overhead.

You can easily imagine how to create a DYNAMIC_CAST macro that uses the above scheme and allows an easier transition to the built-in dynamic_cast operator.

Contents | Prev | Next


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