C++ – Safely checking the type of a variable

cdynamic-cast

For a system I need to convert a pointer to a long then the long back to the pointer type. As you can guess this is very unsafe. What I wanted to do is use dynamic_cast to do the conversion so if I mixed them I'll get a null pointer. This page says http://publib.boulder.ibm.com/infocenter/lnxpcomp/v7v91/index.jsp?topic=/com.ibm.vacpp7l.doc/language/ref/clrc05keyword_dynamic_cast.htm

The dynamic_cast operator performs
type conversions at run time. The
dynamic_cast operator guarantees the
conversion of a pointer to a base
class to a pointer to a derived class,
or the conversion of an lvalue
referring to a base class to a
reference to a derived class. A
program can thereby use a class
hierarchy safely. This operator and
the typeid operator provide run-time
type information (RTTI) support in
C++.

and I'd like to get an error if it's null so I wrote my own dynamic cast

template<class T, class T2> T mydynamic_cast(T2 p)
{
    assert(dynamic_cast<T>(p));
    return reinterpret_cast<T>(p);
}

With MSVC I get the error "error C2681: 'long' : invalid expression type for dynamic_cast". It turns out this will only work with classes which have virtual functions… WTF! I know the point of a dynamic cast was for the up/down casting inheritance problem but I also thought it was to solve the type cast problem dynamically. I know I could use reinterpret_cast but that doesn't guarantee the same type of safety.

What should I use to check if my typecast are the same type? I could compare the two typeid but I would have a problem when I want to typecast a derived to its base. So how can I solve this?

Best Answer

I've had to do similar things when loading C++ DLLs in apps written in languages that only support a C interface. Here is a solution that will give you an immediate error if an unexpected object type was passed in. This can make things much easier to diagnose when something goes wrong.

The trick is that every class that you pass out as a handle has to inherit from a common base class.

#include <stdexcept>
#include <typeinfo>
#include <string>
#include <iostream>
using namespace std;


// Any class that needs to be passed out as a handle must inherit from this class.
// Use virtual inheritance if needed in multiple inheritance situations.
class Base
{

public:
    virtual ~Base() {} // Ensure a v-table exists for RTTI/dynamic_cast to work
};


class ClassA : public Base
{

};

class ClassB : public Base
{

};

class ClassC
{
public:
    virtual ~ClassC() {}
};

// Convert a pointer to a long handle.  Always use this function
// to pass handles to outside code.  It ensures that T does derive
// from Base, and that things work properly in a multiple inheritance
// situation.
template <typename T>
long pointer_to_handle_cast(T ptr)
{
    return reinterpret_cast<long>(static_cast<Base*>(ptr));
}

// Convert a long handle back to a pointer.  This makes sure at
// compile time that T does derive from Base.  Throws an exception
// if handle is NULL, or a pointer to a non-rtti object, or a pointer
// to a class not convertable to T.
template <typename T>
T safe_handle_cast(long handle)
{
    if (handle == NULL)
        throw invalid_argument(string("Error casting null pointer to ") + (typeid(T).name()));

    Base *base = static_cast<T>(NULL); // Check at compile time that T converts to a Base *
    base = reinterpret_cast<Base *>(handle);
    T result = NULL;

    try
    {
        result = dynamic_cast<T>(base);
    }
    catch(__non_rtti_object &)
    {
        throw invalid_argument(string("Error casting non-rtti object to ") + (typeid(T).name()));
    }

    if (!result)
        throw invalid_argument(string("Error casting pointer to ") + typeid(*base).name() + " to " + (typeid(T).name()));

    return result;
}

int main()
{
    ClassA *a = new ClassA();
    ClassB *b = new ClassB();
    ClassC *c = new ClassC();
    long d = 0; 


    long ahandle = pointer_to_handle_cast(a);
    long bhandle = pointer_to_handle_cast(b);
    // long chandle = pointer_to_handle_cast(c); //Won't compile
    long chandle = reinterpret_cast<long>(c);
    // long dhandle = pointer_to_handle_cast(&d); Won't compile
    long dhandle = reinterpret_cast<long>(&d);

    // send handle to library
    //...
    // get handle back
    try
    {
        a = safe_handle_cast<ClassA *>(ahandle);
        //a = safe_handle_cast<ClassA *>(bhandle); // fails at runtime
        //a = safe_handle_cast<ClassA *>(chandle); // fails at runtime
        //a = safe_handle_cast<ClassA *>(dhandle); // fails at runtime
        //a = safe_handle_cast<ClassA *>(NULL); // fails at runtime
        //c = safe_handle_cast<ClassC *>(chandle); // Won't compile
    }
    catch (invalid_argument &ex)
    {
        cout << ex.what() << endl;
    }

    return 0;
}