18.2. Run-Time Type Identification
Run-time Type Identification (RTTI) allows programs that use pointers or references to base classes to retrieve the actual derived types of the objects to which these pointers or references refer.RTTI is provided through two operators:
- The typeid operator, which returns the actual type of the object referred to by a pointer or a referenceThe dynamic_cast operator, which safely converts from a pointer or reference to a base type to a pointer or reference to a derived type


18.2.1. The dynamic_cast Operator
The dynamic_cast operator can be used to convert a reference or pointer to an object of base type to a reference or pointer to another type in the same hierarchy. The pointer used with a dynamic_cast must be validit must either be 0 or point to an object.Unlike other casts, a dynamic_cast involves a run-time type check. If the object bound to the reference or pointer is not an object of the target type, then the dynamic_cast fails. If a dynamic_cast to a pointer type fails, the result of the dynamic_cast is the value 0. If a dynamic_cast to a reference type fails, then an exception of type bad_cast is thrown.The dynamic_cast operator therefore performs two operations at once. It begins by verifying that the requested cast is valid. Only if the cast is valid does the operator actually do the cast. In general, the type of the object to which the reference or pointer is bound isn't known at compile-time. A pointer to base can be assigned to point to a derived object. Similarly, a reference to base can be initialized by a derived object. As a result, the verification that the dynamic_cast operator performs must be done at run time.
Using the dynamic_cast Operator
As a simple example, assume that Base is a class with at least one virtual function and that class Derived is derived from Base. If we have a pointer to Base named basePtr, we can cast it at run time to a pointer to Derived as follows:
At run time, if basePtr actually points to a Derived object, then the cast will be successful, and derivedPtr will be initialized to point to the Derived object to which basePtr points. Otherwise, the result of the cast is 0, meaning that derivedPtr is set to 0, and the condition in the if fails.
if (Derived *derivedPtr = dynamic_cast<Derived*>(basePtr))
{
// use the Derived object to which derivedPtr points
} else { // BasePtr points at a Base object
// use the Base object to which basePtr points
}


Using a dynamic_cast and Reference Types
In the previous example, we used a dynamic_cast to convert a pointer to base to a pointer to derived. A dynamic_cast can also be used to convert a reference to base to a reference to derived. The form for this a dynamic_cast operation is the following,
where Type is the target type of the conversion, and val is an object of base class type.The dynamic_cast operation converts the operand val to the desired type Type& only if val actually refers to an object of the type Type or is an object of a type derived from Type.Because there is no such thing as a null reference, it is not possible to use the same checking strategy for references that is used for pointer casts. Instead, when a cast fails, it throws a std::bad_cast exception. This exception is defined in the typeinfo library header.We might rewrite the previous example to use references as follows:
dynamic_cast< Type& >(val)
void f(const Base &b)
{
try {
const Derived &d = dynamic_cast<const Derived&>(b);
// use the Derived object to which b referred
} catch (bad_cast) {
// handle the fact that the cast failed
}
}
Exercises Section 18.2.1
Exercise 18.13:Given the following class hierarchy in which each class defines a public default constructor and virtual destructor,
which, if any, of the following dynamic_casts fail?
class A { /* ... */ };
class B : public A { /* ... */ };
class C : public B { /* ... */ };
class D : public B, public A { /* ... */ };
Exercise 18.14:What would happen in the last conversion in the previous exercise if both D and B inherited from A as a virtual base class?Exercise 18.15:Using the class hierarchy defined in the previous exercise, rewrite the following piece of code to perform a reference dynamic_cast to convert the expression *pa to the type C&:
(a) A *pa = new C;
B *pb = dynamic_cast< B* >(pa);
(b) B *pb = new B;
C *pc = dynamic_cast< C* >(pb);
(c) A *pa = new D;
B *pb = dynamic_cast< B* >(pa);
Exercise 18.16:Explain when you would use dynamic_cast instead of a virtual function.
if (C *pc = dynamic_cast< C* >(pa))
// use C's members
} else {
// use A's members
}
18.2.2. The typeid Operator
The second operator provided for RTTI is the typeid operator. The typeid operator allows a program to ask of an expression: What type are you?A typeid expression has the form
where e is any expression or a type name.If the type of the expression is a class type and that class contains one or more virtual functions, then the dynamic type of the expression may differ from its static compile-time type. For example, if the expression dereferences a pointer to a base class, then the static compile-time type of that expression is the base type. However, if the pointer actually addresses a derived object, then the typeid operator will say that the type of the expression is the derived type.The typeid operator can be used with expressions of any type. Expressions of built-in type as well as constants can be used as operands for the typeid operator. When the operand is not of class type or is a class without virtual functions, then the typeid operator indicates the static type of the operand. When the operand has a class-type that defines at least one virtual function, then the type is evaluated at run time.The result of a typeid operation is a reference to an object of a library type named type_info. Section 18.2.4 (p. 779) covers this type in more detail. To use the type_info class, the library header typeinfo must be included.
typeid(e)
Using the typeid Operator
The most common use of typeid is to compare the types of two expressions or to compare the type of an expression to a specified type:
In the first if, we compare the actual types of the objects to which bp and dp point. If they both point to the same type, then the test succeeds. Similarly, the second if succeeds if bp currently points to a Derived object.Note that the operands to the typeid are expressions that are objectswe tested *bp, not bp:
Base *bp;
Derived *dp;
// compare type at run time of two objects
if (typeid(*bp) == typeid(*dp)) {
// bp and dp point to objects of the same type
}
// test whether run time type is a specific type
if (typeid(*bp) == typeid(Derived)) {
// bp actually points to a Derived
}
This test compares the type Base* to type Derived. These types are unequal, so this test will always fail regardless of the type of the object to which bp points.Section 5.8, p. 167) the compiler does not evaluate *p. It uses the static type of p, which does not require that p itself be a valid pointer.
// test always fails: The type of bp is pointer to Base
if (typeid(bp) == typeid(Derived)) {
// code never executed
}
Exercises Section 18.2.2
Exercise 18.17:Write an expression to dynamically cast a pointer to a Query_base to a pointer to an AndQuery. Test the cast by using objects of AndQuery and of another query type. Print a statement indicating whether the cast works and be sure that the output matches your expectations.Exercise 18.18:Write the same cast, but cast a Query_base object to a reference to AndQuery. Repeat the test to ensure that your cast works correctly.Exercise 18.19:Write a typeid expression to see whether two Query_base pointers point to the same type. Now check whether that type is an AndQuery.
18.2.3. Using RTTI
As an example of when RTTI might be useful, consider a class hierarchy for which we'd like to implement the equality operator. Two objects are equal if they have the same value for a given set of their data members. Each derived type may add its own data, which we will want to include when testing for equality.Because the values considered in determining equality for a derived type might differ from those considered for the base type, we'll (potentially) need a different equality operator for each pair of types in the hierarchy. Moreover, we'd like to be able to use a given type as either the left-hand or right-hand operand, so we'll actually need two operators for each pair of types.If our hierarchy has only two types, we need four functions:
But if our hierarchy has several types, the number of operators we must define expands rapidlyfor only 3 types we'd need 9 operators. If the hierarchy has 4 types, we'd need 16, and so on.We might think we could solve this problem by defining a set of virtual functions that would perform the equality test at each level in the hierarchy. Given those virtuals, we could define a single equality operator that operates on references to the base type. That operator could delegate its work to a virtual equal operation that would do the real work.Unfortunately, virtual functions are not a good match to this problem. The trouble is deciding on the type for the parameter to the equal operation. Virtual functions must have the same parameter type(s) in both the base and derived classes. That implies that a virtual equal operation must have a parameter that is a reference to the base class.However, when we compare two derived objects, we want to compare data members that might be particular to that derived class. If the parameter is a reference to base, we can use only members that are present in the base class. We cannot access members that are in the derived class but not in the base.Thinking about the problem in this detail, we see that we want to return false if we attempt to compare objects of different types. Given this observation, we can now use RTTI to solve our problem.We'll define a single equality operator. Each class will define a virtual equal function that first casts its operand to the right type. If the cast succeeds, then the real comparison will be performed. If the cast fails, then the equal operation will return false.
bool operator==(const Base&, const Base&)
bool operator==(const Derived&, const Derived&)
bool operator==(const Derived&, const Base&);
bool operator==(const Base&, const Derived&);
The Class Hierarchy
To make the concept a bit more concrete, let's assume that our classes look something like:
class Base {
friend bool operator==(const Base&, const Base&);
public:
// interface members for Base
protected:
virtual bool equal(const Base&) const;
// data and other implementation members of Base
};
class Derived: public Base {
friend bool operator==(const Base&, const Base&);
public:
// other interface members for Derived
private:
bool equal(const Base&) const;
// data and other implementation members of Derived
};
A Type-Sensitive Equality Operator
Next let's look at how we might define the overall equality operator:
This operator returns false if the operands are different types. If they are the same type, then it delegates the real work of comparing the operands to the appropriate virtual equal function. If the operands are Base objects, then Base::equal will be called. If they are Derived objects, Derived::equal is called.
bool operator==(const Base &lhs, const Base &rhs)
{
// returns false if typeids are different otherwise
// returns lhs.equal(rhs)
return typeid(lhs) == typeid(rhs) && lhs.equal(rhs);
}
The Virtual equal Functions
Each class in the hierarchy must define its own version of equal. The functions in the derived classes will all start the same way: They'll cast their argument to the type of the class itself:
The cast should always succeedafter all, the function is called from the equality operator only after testing that the two operands are the same type. However, the cast is necessary so that the function can access the derived members of the right-hand operand. The operand is a Base&, so if we want to access members of the Derived, we must first do the cast.
bool Derived::equal(const Base &rhs) const
{
if (const Derived *dp
= dynamic_cast<const Derived*>(&rhs)) {
// do work to compare two Derived objects and return result
} else
return false;
}
The Base-Class equal Function
This operation is a bit simpler than the others:
There is no need to cast the parameter before using it. Both *this and the parameter are Base objects, so all the operations available for this object are also defined for the parameter type.
bool Base::equal(const Base &rhs) const
{
// do whatever is required to compare to Base objects
}
18.2.4. The type_info Class
The exact definition of the type_info class varies by compiler, but the standard guarantees that all implementations will provide at least the operations listed in Table 18.2
Table 18.2. Operations on type_info
t1 == t2Returns true if the two type_info objects t1 and t2 refer to the same type; false otherwise.t1 != t2Returns TRue if the two type_info objects t1 and t2 refer to different types; false otherwise.t.name()Returns a C-style character string that is a printable version of the type name. Type names are generated in a system-dependent way.t1.before(t2)Returns a bool that indicates whether t1 comes before t2. The ordering imposed by before is compiler-dependent.The class also provides a public virtual destructor, because it is intended to serve as a base class. If the compiler wants to provide additional type information, it should do so in a class derived from type_info.The default and copy constructors and the assignment operator are all defined as private, so we cannot define or copy objects of type type_info. The only way to create type_info objects in a program is to use the typeid operator.The name function returns a C-style character string for the name of the type represented by the type_info object. The value used for a given type depends on the compiler and in particular is not required to match the type names as used in a program. The only guarantee we have about the return from name is that it returns a unique string for each type. Nonetheless, the name member can be used to print the name of a type_info object:The format and value returned by name varies by compiler. This program, when executed on our machine, generates the following output:
int iobj;
cout << typeid(iobj).name() << endl
<< typeid(8.16).name() << endl
<< typeid(std::string).name() << endl
<< typeid(Base).name() << endl
<< typeid(Derived).name() << endl;
i
d
Ss
4Base
7Derived
