Thursday 17 April 2014

Casting in C++

Unlike the sort of implicit casting style in C, C++ introduced 4 types of casting. And they can be categorized into different scenarios,  be better suited for purpose and provide better communication between programmers about when they want to do.

1. static_cast
static_cast can be regarded as a simple replacement of C style casting. Anything used to be done in C casting can be replaced by static cast. For instance
- type promotion (int <-> float)
- cast from non-const type into const type
- up-casting
- down-casting if you know the type, faster than dynamic cast

2. const_cast
const_cast is only used for casting const-ness away. This (casting const-ness away) should not happen in most cases and most of occasions they can be considered as bugs. If possible, use better design or replace with "mutable" if situation applies.

*********************************************************************************
const int i = 0;
const int& iRef = i;
const_cast<int&> iRef = 1; // modify the const value of "i"
                           // from 0 to 1
*********************************************************************************

This sort of hack should not happen at the first place by not declaring i as const.

There is only one legitimate usage of const_cast in my opinion. It is to reduce the code duplication between const and non-const versions of function, if these two functions simply share most parts of common.

******************************************************************
class Bar;
class Foo {
public:
 
    const Bar& GetBar() const {
/*
         * implementation
         */
    }

    // have the same implementation as its const version
    Bar& GetSize() {
return const_cast<Bar&>(static_cast<const Foo&>(*this).GetBar());
    }
};
******************************************************************

The solution is to cast Foo itself into a const version to be able to call the const version o function GetBar(). Then cast away the const-ness of the return value of const version of GetBar() and return it.

3. dynamic_cast
dynamic_cast is the only type of casting which C's style cast can't do. This is no surprise because C is a procedural programming language and OOP is out of its scope.

dynamic_cast is introduced only for down-casting from base class to derived class. static_cast can be used in down casting as well  but only when you are sure that will succeed. Otherwise it fails you at the run time. However dynamic_cast provides more than static_cast can do, it will provide feedback of the casting and let the programmers to test if it is a successful casting.

dynamic_cast fails to work on non-polymorphic class
******************************************************************************
class Base {
public:
    // no virtual function
    const char* GetName() const;
};

class Derived : Base {
public:
    // it can have virtual function
    const char* GetID() const;
    virtual const int GetSize() const;
};

Base b;

/******************* dynamic cast example ********************/
// ERROR: compiler complains that
// Base is not polymorphic, because
// it does not have virtual function
Derived* dPtr1 = dynamic_cast<Derived*>(&b);
Derived& dRef1 = dynamic_cast<Derived&>(b);

/******************* static cast example ********************/
// OK: compiler gives green right to the code
// But fails at the run-time, when calling function 
// only available from Derived
Derived* dPtr2 = static_cast<Derived*>(&b);  // OK
dPtr2->GetName()                             // OK
dPtr2->GetID();                              // CRASH!!!
Derived* dRef2 = static_cast<Derived&>(b);   // OK
dRef2.GetName();             // OK
dRef2.GetID();                               // CRASH!!!  
******************************************************************************

A few things we can learn from this example
- dynamic_cast does not work with non-polymorphic class (that does not have virtual functions). Because the information stored in virtual table has to be retrieved to decide its actual type at the run time. See my other blog entry about it, http://cpluspluslearning-petert.blogspot.co.uk/2014/04/pure-virtual-functions.html.
- static_cast does not always work well with down-casting. It could cause crashing if two types do not match.
- Do not use inheritance if the base class does not have virtual functions (non-polymorphic). So inheriting Derived from Base might not be a good design. Think about other desing pattern, like compoition.

dynamic_cast works well on polymorphic class
******************************************************************************
class Base {
public:
    // must have virtual function
    virtual const char* GetName() const;
};

class Derived : Base {
public:
    // it can have virtual function
    virtual const char* GetName() const;
    const char* GetID() const;
    virtual const int GetSize() const;
};

Base b;

/******************* dynamic cast example ********************/
Derived* dPtr1 = dynamic_cast<Derived*>(&b); // OK
dPtr1.GetID();                                                      // CRASH!!!
                                                                            // have to test against nullptr
Derived& dRef1 = dynamic_cast<Derived&>(b);  // EXCEPTION: bad_cast
if (dPtr1) {                                                            // Evaluated as "false"
   dPtr1.GetID();                                                   // Safe to carry on
}


/******************* static cast example ********************/
// OK: compiler gives green right to the code
// But fails at the run-time, when calling function 
// only available from Derived
Derived* dPtr2 = static_cast<Derived*>(&b);  // OK
dPtr2->GetName()                             // OK
dPtr2->GetID();                              // CRASH!!!
Derived* dRef2 = static_cast<Derived&>(b);   // OK
dRef2.GetName();             // OK
dRef2.GetID();                               // CRASH!!!  
******************************************************************************

Here is a few things I can learn from this code snippet
- dynamic_cast has to work with polymorphic classes (that must have virtual functions.)
- dynamic_cast provides facility to test if its casting is successful.
- dynamic_cast generates std::bad_cast if failing casting into a reference
- dynamic_cast will return a null pointer if failing casting into a pointer
- static_cast works on class no matter if it's polymorphic but will crash if calling some functions it doesn't have.
- dynamic_cast works slower than static_cast

4. reinterpret_cast
reinterpret_cast can be used to cast anything to anything. (Of course, this can be achieved by the bridge void* and static_cast. Anything can be casted into void* and void* can be cast into anything).

******************************************************************************
class Foo {
};
class Bar {
};

Foo f;
B* bPtr = reinterpret_cast<B*>(&f);

// equveliant to
void* br = static_cast<void*>(*f);
B* bPtr = static_cast<B*>(br);
******************************************************************************

Any of operation of reinterpret casting will succeed, but it will crash when invoking functions if the two type does not match.

reinterpret_cast is not used that often in daily work. But I did use it when I was working on legacy code and TCP/IP application in my previous projects. For instance I use reinterpret_cast to casting an integer to a pointer of a struct between CORBA interface to C++ code. And cast a data payload into a data struct on TCP/IP application. The bottom line is that you have to be hundred percents sure that the data type you are casting between matching exactly, otherwise it will bite back.

No comments:

Post a Comment