Saturday 28 May 2011

C++ Up-Casting & Down-Casting in Inheritance Hierarchy


1. Upcasting / Down Casting in Brief

Upcasting : Upcasting is casting a derived class object to its Base Class Object. This is casting up the hierarchy and this is always allowed as the Base class object is subclass of the derived class.








Figure 1 : Inheritance hierarchy

D* pd = new D();
B* pb = pd;

DownCasting : DownCasting is casting a base class object pointer to its derived class object pointer. As the actual object contained in the derived class object pointer can be the Base class object or the derived class object . Therefore the compiler does  not performs the implicit conversion.
         DownCasting requires explicit casting by the user. static_cast and dynamic_cast are used for this purpose.

static_cast : Static cast is unsafe cast and does not performs any check if the object being converted is complete. As long as the conversion is in the same Inheritance hierarchy the code compiles. Since this does not has any overhead it is not expensive.

dynamic_cast : dynamic_cast is a safe cast as it performs a check if the object being converted  to is complete. If the check fails it returns NULL in pointer conversion and 'bad_cast' exception in reference conversion.
    dynamic_cast can only be used in polymorphic classes because it consults VTABLE to perform the check.

2. Casting in Single Inheritance :
   Casting in single inheritance is straightforward and does not needs any pointer arithmetic to be performed on the part of the compiler.

class A
{
public:
     virtual void foo();
};
class B : public A
{
public:
     virtual void foo();
};







Figure 2 : Memory Layout of object B

B *pb = new B();
A *pa = pb; // Upcasting from B to A.

For the Single Inheritance case Upcasting/Downcasting does not requires any pointer arithmetic, as both pointer to A and pointer to B points to the same location in memory. As shown in the above memory layout of the object B.


3. Casting in Multiple Inheritance :

Casting in multiple inheritance requires movement of pointer by some offset ( for the classes on the right hand side of the multiple inheritance). Lets see this in the below example


        









Figure 3: Memory Layout of Object D.

The contrast difference between single inheritance and multiple inheritance is in single inheritance we have one 'vptr' for an object irrespective of how deep down it is in inheritance hierarchy. Whereas in multiple Inheritance we have 'vptr' for each of the hierarchial path. In the above case the object D has 2 vptr.
Thus casting in multiple inheritance requires addition/subtraction of offset by the compiler.

D *pd = new D();
A *pa = pd;    // No pointer arithmetic pa points to same location as pd.
B *pb = pd;   // No pointer arithmetic pb points to same location as pd.
C *pc = pd;   // Compiles fine, but compiler has to subtract( in few cases add based on the memory layout )
                     //  offset from the D pointer in order to point pc to the Object C location. The pc location is
                     //  shown in the memory model.
All of the above compiles, whereas in the third case the compiler had to do pointer arithmetic as the Object C in the object model is embedded in the object D and does not shares A & B address space. Thus the pc does not points to the same location as that of 'pd'.

DownCasting :
D *pd = static_cast<D*>(pa);   // No Pointer Arithmetic performed by compiler, pa and pd points to same location
D *pd = static_cast<D*>(pb);  // No Pointer Arithmetic performed by compiler, pb and pd points to same location
D *pd = static_cast<D*>(pc);  // compiler had to adjust the pointer ( add/subtract ) in order to point it to D object address.

In the case 1 and 2 no change in the pointer whereas in case 3, pointer has to be adjusted to point it to the D's object address.

3. Casting in Virtual Inheritance :
             Whenever we have a common base class in the inheritance hierarchy. Then this common base class is added more than once in the derived class ( i.e added once for each path). In order to have only one instance of Base class we need to use virtual inheritance.















Figure 4: Memory Layout of Object D using Virtual Inheritance

The memory layout of virtual inheritance is not trivial. consult the "virtual inheritance in detail"  section for detailed information.
As the memory layout is not trivial it adds more load on the casting.


Upcasting :

D* pd = new D();
B* pb = pd;    // No Pointer Arithmetic performed by compiler, pb and pd points to same location
C* pc = pd;    // Compiler had to pointer arithmetic in order to point pc to the Object C location.
A* pa = pd;    // Compiler had to pointer arithmetic in order to point pa to the Object A location.

All of the above compiles fine, conversion from pd to pc does not requires any pointer arithmetic.
Whereas conversion from object pointet of D to object pointer of C and object pointer of A required pointer arithmetic to be performed by the compiler.

DownCasting :
   In Single Inheritance and Multiple Inheritance down casting is achieved by static_cast (dynamic_cast is required only when we require safecasting in polymorphic classes).

D *pd1 = static_cast<D*>(pb);  // compiles fine : No pointer arithmetic
D *pd2 = static_cast<D*>(pc);  // compiles fine : but compiler performs pointer arithme
D *pd3 = static_cast<D*>(pa);  // Compilation ERROR

The last statement gives the following error on one of my compilers : "Cannot convert from base A to derived type D via virtual base A". That is pretty clear: Given the layout just shown you can understand why is the error.
you cannot cast from a virtual base class to the derived class using static_cast

We had to chase(pointer to Base A) a pointer to get from a B, C or D oject pointer to the A Sub-Object, but there is no way to go to the opposite direction.
  Whereas the virtual funciton mechanism works properly on A. It recovers object D from pointer to A.

pa->foo();  // calls D::foo()

dynamic_cast is used to convert or Downcast from virtual base class pointer to the derived object pointer.

D *pd3 = dynamic_cast<D*>(pa);

dynamic_cast references the virtual table to get the offset information to convert the A object pointer into D object pointer. It moves the pointer by the offset and returns the pointer to D.

NOTE: In order to use dynamic_cast on the virtual base class. The virtual base class has to be polymorphic.