C++ Tutorial - Dynamic Cast - 2020
RTTI is short for Run-time Type Identification. RTTI is to provide a standard way for a program to determine the type of object during runtime.
In other words, 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 reference).
- 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.
An attempt to convert an object into a more specific object.
Let's look at the code. If you do not understand what's going on, please do not worry, we'll get to it later.
#include <iostream> using namespace std; class A { public: virtual void f(){cout << "A::f()" << endl;} }; class B : public A { public: void f(){cout << "B::f()" << endl;} }; int main() { A a; B b; a.f(); // A::f() b.f(); // B::f() A *pA = &a; B *pB = &b; pA->f(); // A::f() pB->f(); // B::f() pA = &b; // pB = &a; // not allowed pB = dynamic_cast<B*>(&a;); // allowed but it returns NULL return 0; }
The dynamic_cast operator is intended to be the most heavily used RTTI component. It doesn't give us what type of object a pointer points to. Instead, it answers the question of whether we can safely assign the address of an object to a pointer of a particular type.
Unlike other casts, a dynamic_cast involves a run-time type check. If the object bound to the pointer is not an object of the target type, it fails and the value is 0. If it's a reference type when it fails, then an exception of type bad_cast is thrown. So, if we want dynamic_cast to throw an exception (bad_cast) instead of returning 0, cast to a reference instead of to a pointer. Note also that the dynamic_cast is the only cast that relies on run-time checking.
"The need for dynamic_cast generally arises because you want to perform derived class operation on a derived class object, but you have only a pointer or reference-to-base" said Scott Meyers in his book "Effective C++".
Let's look at the example code:
class Base { }; class Derived : public Base { }; int main() { Base b; Derived d; Base *pb = dynamic_cast<Base*>(&d;); // #1 Derived *pd = dynamic_cast<Derived*>(&b;); // #2 return 0; }
The #1 is ok because dynamic_cast is always successful when we cast a class to one of its base classes
The #2 conversion has a compilation error:
error C2683: 'dynamic_cast' : 'Base' is not a polymorphic type.
It's because base-to-derived conversions are not allowed with dynamic_cast unless the base class is polymorphic.
So, if we make the Base class polymorphic by adding virtual function as in the code sample below, it will be compiled successfully.
class Base {virtual void vf(){}}; class Derived : public Base { }; int main() { Base b; Derived d; Base *pb = dynamic_cast<Base*>(&d;); // #1 Derived *pd = dynamic_cast<Derived*>(&b;); // #2 return 0; }
But at runtime, the #2 cast fails and produces null pointer.
Let's look at another example.
class Base { virtual void vf(){} }; class Derived : public Base { }; int main() { Base *pBDerived = new Derived; Base *pBBase = new Base; Derived *pd; pd = dynamic_cast<Derived*>(pBDerived); #1 pd = dynamic_cast<Derived*>(pBBase); #2 return 0; }
The example has two dynamic casts from pointers of type Base to a point of type Derived. But only the #1 is successful.
Even though pBDerived and pBBase are pointers of type Base*, pBDerived points to an object of type Derived, while pBBase points to an object of type Base. Thus, when their respective type-castings are performed using dynamic_cast, pBDerived is pointing to a full object of class Derived, whereas pBBase is pointing to an object of class Base, which is an incomplete object of class Derived.
In general, the expression
dynamic_cast<Type *>(ptr)converts the pointer ptr to a pointer of type Type* if the pointer-to object (*ptr) is of type Type or else derived directly or indirectly from type Type. Otherwise, the expression evaluates to 0, the null pointer.
In the code below, there is one function call in main() that's not working. Which one?
#include <iostream> using namespace std; class A { public: virtual void g(){} }; class B : public A { public: virtual void g(){} }; class C : public B { public: virtual void g(){} }; class D : public C { public: virtual void g(){} }; A* f1() { A *pa = new C; B *pb = dynamic_cast<B*>(pa); return pb; } A* f2() { A *pb = new B; C *pc = dynamic_cast<C*>(pb); return pc; } A* f3() { A *pa = new D; B *pb = dynamic_cast<B*>(pa); return pb; } int main() { f1()->g(); // (1) f2()->g(); // (2) f3()->g(); // (3) return 0; }
Answer (2). It's a downcasting.
In this example, the DoSomething(Window* w) is passed down Window pointer. It calls scroll() method which is only available from Scroll object. So, in this case, we need to check if the object is the Scroll type or not before the call to the scroll() method.
#include <iostream> #include <string> using namespace std; class Window { public: Window(){} Window(const string s):name(s) {}; virtual ~Window() {}; void getName() { cout << name << endl;}; private: string name; }; class ScrollWindow : public Window { public: ScrollWindow(string s) : Window(s) {}; ~ScrollWindow() {}; void scroll() { cout << "scroll()" << endl;}; }; void DoSomething(Window *w) { w->getName(); // w->scroll(); // class "Window" has no member scroll // check if the pointer is pointing to a scroll window ScrollWindow *sw = dynamic_cast<ScrollWindow*>(w); // if not null, it's a scroll window object if(sw) sw->scroll(); } int main() { Window *w = new Window("plain window"); ScrollWindow *sw = new ScrollWindow("scroll window"); DoSomething(w); DoSomething(sw); return 0; }
Converting a derived-class reference or pointer to a base-class reference or pointer is called upcasting. It is always allowed for public inheritance without the need for an explicit type cast.
Actually this rule is part of expressing the is-a relationship. A Derived object is a Base object in that it inherits all the data members and member functions of a Base object. Thus, anything that we can do to a Base object, we can do to a Derived class object.
The downcasting, the opposite of upcasting, is a process converting a base-class pointer or reference to a derived-class pointer or reference.
It is not allowed without an explicit type cast. That's because a derived class could add new data members, and the class member functions that used these data members wouldn't apply to the base class.
Here is a self explanatory example
#include <iostream> using namespace std; class Employee { private: int id; public: void show_id(){} }; class Programmer : public Employee { public: void coding(){} }; int main() { Employee employee; Programmer programmer; // upcast - implicit upcast allowed Employee *pEmp = &programmer; // downcast - explicit type cast required Programmer *pProg = (Programmer *)&employee; // Upcasting: safe - progrommer is an Employee // and has his id to do show_id(). pEmp->show_id(); pProg->show_id(); // Downcasting: unsafe - Employee does not have // the method, coding(). // compile error: 'coding' : is not a member of 'Employee' // pEmp->coding(); pProg->coding(); return 0; }
More on Upcasting and Downcasting
typeid operator allows us to determine whether two objects are the same type.
In the previous example for Upcasting and Downcasting, employee gets the method coding() which is not desirable. So, we need to check if a pointer is pointing to the Programmer object before we use the method, coding().
Here is a new code showing how to use typeid:
class Employee { private: int id; public: void show_id(){} }; class Programmer : public Employee { public: void coding(){} }; #include <typeinfo> int main() { Employee lee; Programmer park; Employee *pEmpA = &lee; Employee *pEmpB = &park; // check if two object is the same if(typeid(Programmer) == typeid(lee)) { Programmer *pProg = (Programmer *)&lee; pProg->coding(); } if(typeid(Programmer) == typeid(park)) { Programmer *pProg = (Programmer *)&park; pProg->coding(); } pEmpA->show_id(); pEmpB->show_id(); return 0; }
So, only a programmer uses the coding() method.
Note that we included <typeinfo> in the example. The typeid operator returns a reference to a type_info object, where type_info is a class defined in the typeinfo header file.
This is from Google C++ Style Guide.
RTTI allows a programmer to query the C++ class of an object at run time. This is done by use of typeid or dynamic_cast. Avoid using Run Time Type Information (RTTI).
- Pros
The standard alternatives to RTTI (described below) require modification or redesign of the class hierarchy in question. Sometimes such modifications are infeasible or undesirable, particularly in widely-used or mature code.
RTTI can be useful in some unit tests. For example, it is useful in tests of factory classes where the test has to verify that a newly created object has the expected dynamic type. It is also useful in managing the relationship between objects and their mocks.
RTTI is useful when considering multiple abstract objects. Considerbool Base::Equal(Base* other) = 0; bool Derived::Equal(Base* other) { Derived* that = dynamic_cast<Derived*>(other); if (that == NULL) return false; ... }
- Cons
Querying the type of an object at run-time frequently means a design problem. Needing to know the type of an object at runtime is often an indication that the design of your class hierarchy is flawed.
Undisciplined use of RTTI makes code hard to maintain. It can lead to type-based decision trees or switch statements scattered throughout the code, all of which must be examined when making further changes. - Decision
RTTI has legitimate uses but is prone to abuse, so you must be careful when using it. You may use it freely in unittests, but avoid it when possible in other code. In particular, think twice before using RTTI in new code. If you find yourself needing to write code that behaves differently based on the class of an object, consider one of the following alternatives to querying the type:
- Virtual methods are the preferred way of executing different code paths depending on a specific subclass type. This puts the work within the object itself.
- If the work belongs outside the object and instead in some processing code, consider a double-dispatch solution, such as the Visitor design pattern. This allows a facility outside the object itself to determine the type of class using the built-in type system.
Decision trees based on type are a strong indication that your code is on the wrong track.
if (typeid(*data) == typeid(D1)) { ... } else if (typeid(*data) == typeid(D2)) { ... } else if (typeid(*data) == typeid(D3)) { ...
Code such as this usually breaks when additional subclasses are added to the class hierarchy. Moreover, when properties of a subclass change, it is difficult to find and modify all the affected code segments.
Do not hand-implement an RTTI-like workaround. The arguments against RTTI apply just as much to workarounds like class hierarchies with type tags. Moreover, workarounds disguise your true intent.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization