Tuesday 27 May 2014

C+11 - Runtime Type Information (RTTI)

RTII refers to the facility defined by C++ standard that returns the object's type information at runtime. C++11 standard provides the definitions in <typeinfo>. It includes 3 parts
- class std::type_info
- class std::bad_cast
- class std::bad_typeid;

1. std::type_info
More details refer to C++11 standard.
//********************************************************************************
namespace std {
class type_info {
public:
    virtual ~type_info();
    bool operator==(const type_info& rhs) const noexcept;
    bool operator!=(const type_info& rhs) const noexcept;
    bool before(const type_info& rhs) const noexcept;
    size_t hash_code() const noexcept;
    const char* name() const noexcept;
    type_info(const type_info& rhs) = delete; // cannot be copied
    type_info& operator=(const type_info& rhs) = delete; // cannot be copied
};
}
//********************************************************************************

std::type_info is the return value of typeid() operator. It can be a lvalue or gvalue. And it serves the base class for any other user-defined.

2. Type identification: typeid()
Type identification is used to called on object, object reference and object pointer. cv-specilier does not affect its value. It returns the std::type_info or any of its sub-class.

Example 1: cv specifier and pointer/reference
//********************************************************************************
class Foo {
};

class Bar {
};

void test(void)
{
    Foo foo;
    const Foo fooConst;
    volatile Foo fooVolatile;
    const Foo& fooRef = fooConst;
    Foo* fooPtr = &foo;
    Bar bar;
    std::cout << (typeid(foo) == typeid(fooConst)) << std::endl;     // true
    std::cout << (typeid(foo) == typeid(fooVolatile)) << std::endl;  // true
    std::cout << (typeid(foo) == typeid(fooRef)) << std::endl;        // true
    std::cout << (typeid(foo) == typeid(fooPtr)) << std::endl;         // false
    std::cout << (typeid(foo) == typeid(*fooPtr)) << std::endl;       // true
    std::cout << (typeid(fooPtr) == typeid(fooRef)) << std::endl;   // false
    std::cout << (typeid(*fooPtr) == typeid(fooRef)) << std::endl; // true
    std::cout << (typeid(foo) == typeid(bar)) << std::endl;             // false
}
//********************************************************************************

Example 1 shows that const-volatile specifier does not affect the return value of typeid. Reference type has no effect on the return value of typeid. And the pointer has to de-referenced when used.

Example 2:  base and derived classes without virtual function
//********************************************************************************
class Base {
};

class Derived : public Base {
};

void test()
{
    Base b;
    Derived d;
    Base& bRef = d;
    Base* bPtr = &d;
    std::cout << (typeid(b) == typeid(d)) << std::endl;              // false
    std::cout << (typeid(bRef) == typeid(d)) << std::endl;         // false
    std::cout << (typeid(bPtr) == typeid(d)) << std::endl;          // false
    std::cout << (typeid(*bPtr) == typeid(d)) << std::endl;        // false
    std::cout << (typeid(bRef) == typeid(bPtr)) << std::endl;     // false
    std::cout << (typeid(bRef) == typeid(*bPtr)) << std::endl;   // true
}
//********************************************************************************

Example 3: base and derived classes with virtual function
//********************************************************************************
class Base1 {
public:
    virtual std::string GetName() {
        return "Base1";
    }
};

class Derived1 : public Base1 {
public:
    virtual std::string GetName() {
        return "Derived1";
    }
};

void test()
{
    Base1 b1;
    Derived1 d1;
    Base1& b1Ref = d1;
    Base1* b1Ptr = &d1;
    std::cout << (typeid(b1) == typeid(d1)) << std::endl;              // false
    std::cout << (typeid(b1Ref) == typeid(d1)) << std::endl;         // true
    std::cout << (typeid(b1Ptr) == typeid(d1)) << std::endl;          // false
    std::cout << (typeid(*b1Ptr) == typeid(d1)) << std::endl;        // true
    std::cout << (typeid(b1Ref) == typeid(b1Ptr)) << std::endl;     // false
    std::cout << (typeid(b1Ref) == typeid(*b1Ptr)) << std::endl;   // true
}
//********************************************************************************

Example 2 and Example 3 show the importance of virtual function in the inheritance. Virtual function affects the memory map of object. And the information in the virtual table will be accessed when calling on typeid() operator. Please refer to my other blog entries, virtual table and (pure) virtual function.

3. dynamic_cast and std::bad_cast
Please refer to my other blog entry, Casting in C++.

4. Exceptions: std::bad_typeid and std::bad_cast
std::bad_typeid is thrown when typeid() is called on a nullptr. And std::bad_cast is discussed in my other blog entry, Casting in C++.

//********************************************************************************
class Foo {
};

void test
{
    Foo* fooPtr = nullptr;
    typeid(fooPtr);            // throw std::bad_typeid
}
//********************************************************************************

5. Calling in constructor and deconstructor
C++11 standard says that their behavior is undefined, if dynamic_cast and typeid() is called on an object whose life does not start yet or comes into its end. This is because the virtual table is not correctly written before coming out of constructor and the virtual table may lose information when coming into destructor.  Please refer to my other blog entry, The order of object initialization/destruction.

6. dynamic_cast vs. typeid
typeid and std::bad_typeid is rarely used in C++ programming. However dynamic_cast sometimes have to be used in rare occasions. (If any of them is used very often, it indicates that it is a bad software design.) For instance used with boost::Any to find out the exactly type.
Performance-wise dynamic_cast and typeid are identical because both of them have to access the virtual table. And its performance is equal to the pointer de-referencing to the virtual table.

//********************************************************************************
class Base {
// with virtual functions
};
class Derived : public Base {
// with virtual functions
};

Base* bPtr = new Derived;

// typeid + static_cast
if (typeid(*bPtr) == typeid(Derived)) {
    Derived* dPtr = static_cast<Derived*> (bPtr);
    // do stuff with dPtr
}

// dynamic casting
Derived* dPtr = dynamic_cast<Derived*> (bPtr);
if (dPtr) {
    // do stuff with dPtr;
}
//********************************************************************************

These two solution have the identical performance.

Bioboograpby:
[1] C++ 11 Standard

No comments:

Post a Comment