Section 18.2. Run-Time Type Identification


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:

  1. The typeid operator, which returns the actual type of the object referred to by a pointer or a reference

  2. The dynamic_cast operator, which safely converts from a pointer or reference to a base type to a pointer or reference to a derived type

These operators return dynamic type information only for classes with one or more virtual functions. For all other types, information for the static (i.e., compile-time) type is returned.



The RTTI operators execute at run time for classes with virtual functions, but are evaluated at compile time for all other types.

Dynamic casts are needed when we have a reference or pointer to a base class but need to perform operations from the derived class that are not part of the base class. Ordinarily, the best way to get derived behavior from a pointer to base is to do so through a virtual function. When we use virtual functions, the compiler automatically selects the right function according to the actual type of the object.

In some situations however, the use of virtual functions is not possible. In these cases, RTTI offers an alternate mechanism. However, this mechanism is more error-prone than using virtual member functions: The programmer must know to which type the object should be cast and must check that the cast was performed successfully.

Dynamic casts should be used with caution. Whenever possible, it is much better to define and use a virtual function rather than to take over managing the types directly.



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:

      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      } 

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.

We can apply a dynamic_cast to a pointer whose value is 0. The result of doing so is 0.



By checking the value of derivedPtr, the code inside the if knows that it is operating on a Derived object. It is safe for that code to use Derived operations. If the dynamic_cast fails because basePtr refers to a Base object, then the else clause does processing appropriate to Base instead. The other advantage of doing the check inside the if condition is that it is not possible to insert code between the dynamic_cast and testing the result of the cast. It is, therefore, not possible to use the derivedPtr inadvertently before testing that the cast was successful. A third advantage is that the pointer is not accessible outside the if. If the cast fails, then the unbound pointer is not available for use in later cases where the test might be forgotten.

Performing a dynamic_cast in a condition ensures that the cast and test of its result are done in a single expression.



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,

      dynamic_cast< Type& >(val) 

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:

      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,

      class A { /* ... */ };      class B : public A { /* ... */ };      class C : public B { /* ... */ };      class D : public B, public A { /* ... */ }; 

which, if any, of the following dynamic_casts fail?

      (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.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&:

      if (C *pc = dynamic_cast< C* >(pa))          // use C's members      } else {          // use A's members      } 

Exercise 18.16:

Explain when you would use dynamic_cast instead of a virtual function.


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

      typeid(e) 

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.

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:

      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      } 

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:

      // test always fails: The type of bp is pointer to Base      if (typeid(bp) == typeid(Derived)) {           // code never executed      } 

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.

Dynamic type information is returned only if the operand to typeid is an object of a class type with virtual functions. Testing a pointer (as opposed to the object to which the pointer points) returns the static, compile-time type of the pointer.



If the value of a pointer p is 0, then typeid(*p) throws a bad_typeid exception if the type of p is a type with virtual functions. If the type of p does not define any virtuals, then the value of p is irrelevant. As when evaluating a sizeof expression (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.

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:

      bool operator==(const Base&, const Base&)      bool operator==(const Derived&, const Derived&)      bool operator==(const Derived&, const Base&);      bool operator==(const Base&, const Derived&); 

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.

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:

      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);      } 

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.

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:

      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 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.

The Base-Class equal Function

This operation is a bit simpler than the others:

      bool Base::equal(const Base &rhs) const      {           // do whatever is required to compare to Base objects      } 

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.

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 == t2

Returns true if the two type_info objects t1 and t2 refer to the same type; false otherwise.

t1 != t2

Returns 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:

      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; 

The format and value returned by name varies by compiler. This program, when executed on our machine, generates the following output:

      i      d      Ss      4Base      7Derived 

The type_info class varies by compiler. Some compilers provide additional member functions that provide additional information about types used in a program. You should consult the reference manual for your compiler to understand the exact type_info support provided.





C++ Primer
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2006
Pages: 223
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net