Tuesday, July 5, 2016

dynamic_cast vs static_cast

In c++, it's legal to assign a derived class object to a base class object. This is because the relation of inheritance is "is-a". Derived class object is a base class object as well. Of course, doing so may cause object slicing. Assume we have BaseClass and DerivedClass defined like this.
class BaseClass{
    int x;
};

class DerivedClass : public BaseClass {
    int y;
};

Here are some examples of valid and invalid operations.
DerivedClass derived_class;
BaseClass& base_class = derived_class; // Valid operation
cout<<base_class.x; // Valid operation
cout<<base_class.y; // Invalid, y belongs to DerivedClass and is sliced off
DerivedClass new_derived_class = base_class; // Invalid 1)
DerivedClass& new_derived_class = base_class; // Invalid 2)
DerivedClass* new_derived_class = base_class; // Invalid 3)

Sometimes, we know the reference/pointer of the base class object actually holds the derived class object. When we want to assign it to a derived class object, it's required to explicitly call cast. Both dynamic_cast and static_cast are suitable for the conversion. They can downcast reference and pointer of base class object to derived class object. The differences of the two casts are as follows.

dynamic_cast
  • The base class needs to be polymorphic (There is at least one virtual member function in the class).
  • There is run-time check to make sure the base class object can be casted to derived class object.
  • The run-time incurs run-time overhead.
static_cast
  • The bass class is not required to be polymorphic. The compiler runs the check at compile time and the compiler assumes the user is aware that the cast is always valid.
  • Since there is no run-time check, it still compiles even when the cast is invalid.
Some examples to illustrate the above characteristics. 

DerivedClass& new_derived_class = static_cast<DerivedClass&>(base_class); // Valid
DerivedClass* new_derived_class = static_cast<DerivedClass*>(base_class); // Valid

DerivedClass& new_derived_class = dynamic_cast<DerivedClass&>(base_class); // Invalid, BaseClass is not polymorphic
DerivedClass* new_derived_class = dynamic_cast<DerivedClass*>(base_class); // Invalid, BaseClass is not polymorphic

If we make BaseClass polymorphic by adding a virtual function in it, the two dynamic_cast lines above will also become valid.
class BaseClass{
    virtual ~BaseClass() {}
    int x;
};

When using static_cast, it's the user's responsibility to guarantee the conversion is valid. To the contrast, dynamic_cast runs run-time check to make sure the conversion is valid.
BaseClass base_class;
/* The following two lines compile and run perfectly,
 * though the output value doesn't make any sense,
 * since we are assigning a real BaseClass object to
 * DerivedClass object.
 */
DerivedClass& derived_class = static_cast<DerivedClass&>(base_class);
cout<<derived_class.y;

/* The following one line compiles correctly, but will generate
 * run time exception since dynamic_cast has run-time check.
 */
DerivedClass& derived_class = dynamic_cast<DerivedClass&>(base_class);