Tuesday, 10 December 2013

Using different methods of casting in C++

In C++ there are several types of casting that you can use.

static_cast

static_cast is used for cases where you basically want to reverse an implicit conversion, with a few restrictions and additions. static_cast performs no runtime checks. This should be used if you know that you refer to an object of a specific type, and thus a check would be unnecessary. Example:
 
void func(void *data) {
  // conversion from MyClass* -> void* is implicit
  MyClass *c = static_cast<MyClass*>(data);
  ...
}

int main() {
  MyClass c;
  start_thread(&func, &c)  // func(&c) will be called
      .join();
}
In this example, you know that you passed a MyClass object, and thus there is no need for a runtime check to ensure this.

dynamic_cast

dynamic_cast is used for cases where you don't know what the dynamic type of the object is. You cannot use dynamic_cast if you downcast and the argument type is not polymorphic. An example that checks the object type before performing an operation.

if(JumpStm *j = dynamic_cast<JumpStm*>(&stm)) {
  ...
} else if(ExprStm *e = dynamic_cast<ExprStm*>(&stm)) { 
  ...
}
 
dynamic_cast returns a null pointer if the object referred to doesn't contain the type casted to as a base class (when you cast to a reference, a bad_cast exception is thrown in that case).
The following code is not valid, because Base is not polymorphic (doesn't contain a virtual function):

struct Base { };
struct Derived : Base { };
int main() { 
  Derived d;  
  Base *b = &d;
  dynamic_cast<Derived*>(b); // invalid
}
An "up-cast" is always valid with both static_cast and dynamic_cast, and also without any cast, as an "up-cast" is an implicit conversion.

Regular Cast

These casts are also called c-style cast. A c-style cast is basically identical to trying out a range of sequences of C++ casts, and taking the first c++ cast that works, without ever considering dynamic_cast. Needless to say that this is much more powerful as it combines all of const_cast, static_cast and reinterpret_cast, but it's also unsafe because it does not use dynamic_cast.
In addition, C-style casts not only allow you to do this, but also allow you to safely cast to a private base-class, while the "equivalent" static_cast sequence would give you a compile time error for that.
Some people prefer c-style casts because of their brevity. I use them for numeric casts only, and use the appropriate C++ casts when user defined types are involved, as they provide stricter checking.

reinterpret_cast

reinterpret_cast is necessary is when interfacing with opaque data types. This occurs frequently in vendor APIs over which the programmer has no control. Here's a contrived example where a vendor provides an API for storing and retrieving arbitrary global data:

// vendor.hpp
typedef struct _Opaque * VendorGlobalUserData;
void VendorSetUserData( VendorGlobalUserData p );
VendorGlobalUserData VendorGetUserData();
 
To use this API, the programmer must cast their data to VendorGlobalUserData and back again.  

static_cast won't work, one must use reinterpret_cast:

// main.cpp
#include "vendor.hpp"
#include <iostream>
using namespace std;

struct MyUserData {
    MyUserData() : m( 42 ) {}
    int m;
};

int main() {
    MyUserData u;

    // store global data
    VendorGlobalUserData d1;
 
//  d1 = &u;                                             // compile error
 
//  d1 = static_cast< VendorGlobalUserData >( &u );      // compile error
 
    d1 = reinterpret_cast< VendorGlobalUserData >( &u ); // ok
 
    VendorSetUserData( d1 );

    // do other stuff...

    // retrieve global data 
    VendorGlobalUserData d2 = VendorGetUserData();
    MyUserData * p = 0;
 
//  p = d2;                                              // compile error 
 
//  p = static_cast< MyUserData * >( d2 );               // compile error 
 
    p = reinterpret_cast< MyUserData * >( d2 );          // ok

    if ( p ) { cout << p->m << endl; }
    return 0;
}
 
Below is a contrived implementation of the sample API:

// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData( VendorGlobalUserData p ) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }

No comments:

Post a Comment