Search code examples
c++c++11type-erasure

What's the cost of typeid?


I'm considering a type erasure setup that uses typeid to resolve the type like so...

struct BaseThing
{
    virtual ~BaseThing() = 0 {}
};

template<typename T>
struct Thing : public BaseThing
{
    T x;
};

struct A{};
struct B{};

int main() 
{
    BaseThing* pThing = new Thing<B>();
    const std::type_info& x = typeid(*pThing);

    if( x == typeid(Thing<B>))
    {
        std::cout << "pThing is a Thing<B>!\n";
        Thing<B>* pB = static_cast<Thing<B>*>(pThing);
    }
    else if( x == typeid(Thing<A>))
    {
        std::cout << "pThing is a Thing<A>!\n";
        Thing<A>* pA = static_cast<Thing<A>*>(pThing);
    }
}

I've never seen anyone else do this. The alternative would be for BaseThing to have a pure virtual GetID() which would be used to deduce the type instead of using typeid. In this situation, with only 1 level of inheritance, what's the cost of typeid vs the cost of a virtual function call? I know typeid uses the vtable somehow, but how exactly does it work?

This would be desirable instead of GetID() because it takes quite a bit of hackery to try to make sure the IDs are unique and deterministic.


Solution

  • Typically, you don't just want to know the type, but also do something with the object as that type. In that case, dynamic_cast is more useful:

    int main() 
    {
        BaseThing* pThing = new Thing<B>();
    
        if(Thing<B>* pThingB = dynamic_cast<Thing<B>*>(pThing)) {
        {
            // Do something with pThingB
        }
        else if(Thing<A>* pThingA = dynamic_cast<Thing<A>*>(pThing)) {
        {
            // Do something with pThingA
        }
    }
    

    I think this is why you rarely see typeid used in practice.

    Update:

    Since this question concerns performance. I ran some benchmarks on g++ 4.5.1. With this code:

    struct Base {
      virtual ~Base() { }
      virtual int id() const = 0;
    };
    
    template <class T> struct Id;
    
    template<> struct Id<int> { static const int value = 1; };
    template<> struct Id<float> { static const int value = 2; };
    template<> struct Id<char> { static const int value = 3; };
    template<> struct Id<unsigned long> { static const int value = 4; };
    
    template <class T>
    struct Derived : Base {
      virtual int id() const { return Id<T>::value; }
    };
    
    static const int count = 100000000;
    
    static int test1(Base *bp)
    {
      int total = 0;
      for (int iter=0; iter!=count; ++iter) {
        if (Derived<int>* dp = dynamic_cast<Derived<int>*>(bp)) {
          total += 5;
        }
        else if (Derived<float> *dp = dynamic_cast<Derived<float>*>(bp)) {
          total += 7;
        }
        else if (Derived<char> *dp = dynamic_cast<Derived<char>*>(bp)) {
          total += 2;
        }
        else if (
          Derived<unsigned long> *dp = dynamic_cast<Derived<unsigned long>*>(bp)
        ) {
          total += 9;
        }
      }
      return total;
    }
    
    static int test2(Base *bp)
    {
      int total = 0;
      for (int iter=0; iter!=count; ++iter) {
        const std::type_info& type = typeid(*bp);
    
        if (type==typeid(Derived<int>)) {
          total += 5;
        }
        else if (type==typeid(Derived<float>)) {
          total += 7;
        }
        else if (type==typeid(Derived<char>)) {
          total += 2;
        }
        else if (type==typeid(Derived<unsigned long>)) {
          total += 9;
        }
      }
      return total;
    }
    
    static int test3(Base *bp)
    {
      int total = 0;
      for (int iter=0; iter!=count; ++iter) {
        int id = bp->id();
        switch (id) {
          case 1: total += 5; break;
          case 2: total += 7; break;
          case 3: total += 2; break;
          case 4: total += 9; break;
        }
      }
      return total;
    }
    

    Without optimization, I got these runtimes:

    test1: 2.277s
    test2: 0.629s
    test3: 0.469s
    

    With optimization -O2, I got these runtimes:

    test1: 0.118s
    test2: 0.220s
    test3: 0.290s
    

    So it appears that dynamic_cast is the fastest method when using optimization with this compiler.