Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

C++: Custom RTTI

5.00/5 (4 votes)
12 Apr 2016CPOL1 min read 32.5K   318  
This tip presents a custom RTTI class which is 10x faster than dynamic_cast

This tip presents a intrusive custom RTTI class which provides better performance than dynamic_cast. This class is found in the Real-Time 3D Rendering with DirectX and HLSL by Paul Varcholik. Benchmark shows 10x performance lead over dynamic_cast. For those who may not be familiar, dynamic_cast is used in C++ to do downcast to derived class. For upcast to base class generally do not need any explicit casting. Runtime Type Information (RTTI) must be enabled in the compiler to use dynamic_cast.

Benchmark under VC2010 Windows
     DynamicCast: 1266ms
          AsCast:  127ms

Below shows the entire RTTI class which other class needs to be inherited from. User has to define RTTI_DECLARATIONS macro inside their class.

C++
#pragma once

#include <string>

namespace Library
{
    class RTTI
    {
    public:
        virtual const size_t TypeIdInstance() const = 0;
        
        virtual RTTI* QueryInterface(const size_t)
        {
            return nullptr;
        }
        virtual const RTTI* QueryInterface(const size_t) const
        {
            return nullptr;
        }

        virtual bool Is(const size_t id) const
        {
            return false;
        }

        virtual bool Is(const std::string& name) const
        {
            return false;
        }

        template <typename T>
        T* As() 
        {
            if (Is(T::TypeIdClass()))
            {
                return (T*)this;
            }

            return nullptr;
        }
        template <typename T>
        const T* As() const
        {
            if (Is(T::TypeIdClass()))
            {
                return (T*)this;
            }

            return nullptr;
        }
    };

#define RTTI_DECLARATIONS(Type, ParentType)                            \
    public:                                                            \
        static std::string TypeName() { return std::string(#Type); }   \
        virtual const size_t TypeIdInstance() const                    \
        { return Type::TypeIdClass(); }                                \
        static const size_t TypeIdClass()                              \
        { static int d = 0; return (size_t) &d; }                      \
        virtual Library::RTTI* QueryInterface( const size_t id )       \
        {                                                              \
            if (id == TypeIdClass())                                   \
                { return (RTTI*)this; }                                \
            else                                                       \
                { return ParentType::QueryInterface(id); }             \
        }                                                              \
        virtual const Library::RTTI* QueryInterface( const size_t id ) const \
        {                                                              \
            if (id == TypeIdClass())                                   \
                { return (RTTI*)this; }                                \
            else                                                       \
                { return ParentType::QueryInterface(id); }             \
        }                                                              \
        virtual bool Is(const size_t id) const                         \
        {                                                              \
            if (id == TypeIdClass())                                   \
                { return true; }                                       \
            else                                                       \
                { return ParentType::Is(id); }                         \
        }                                                              \
        virtual bool Is(const std::string& name) const                 \
        {                                                              \
            if (name == TypeName())                                    \
                { return true; }                                       \
            else                                                       \
                { return ParentType::Is(name); }                       \
        }                                                              
}

The trick to uniquely identify a class is by using address of a local static member which should be unique. Credit: Andrew Fedoniouk aka c-smile.

C++
static const size_t TypeIdClass()
{ static int d = 0; return (size_t) &d; }

The usage difference between dynamic_cast and As() member is shown below

C++
Parallelogram* p = dynamic_cast<Parallelogram*>(vec[i]);

Parallelogram* p = vec[i]->As<Parallelogram>();

As the reader may notice As() function do C style cast instead of reinterpret_cast. The compiler will complain of using reinterpret_cast in a const member function.

C++
template <typename T>
const T* As() const
{
    if (Is(T::TypeIdClass()))
    {
        return (T*)this;
    }

    return nullptr;
}

For those who want to use this class in C++98, just change nullptr to NULL. Note: this class does not work in multiple inheritance if more than 1 base class derive from it. Source code is hosted at Github.

History

  • 2015-12-31: Initial release
  • 2016-01-02: Replaced sRunTimeTypeId with a static local variable, so the class is "header only" and Makefile is fixed.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)