什么是typeid的成本?

时间:2011-11-11 13:43:53

标签: c++ c++11 type-erasure

我正在考虑使用typeid来解析类型的类型擦除设置......

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);
    }
}

我从未见过其他人这样做过。替代方案是BaseThing具有纯虚拟GetID(),用于推断类型而不是使用typeid。在这种情况下,只有1级继承,什么是typeid的成本与虚函数调用的成本?我知道typeid以某种方式使用vtable,但它究竟是如何工作的?

这是可取的而不是GetID(),因为尝试确保ID是唯一的确定性需要相当多的hackery。

3 个答案:

答案 0 :(得分:11)

  

替代方案是BaseThing具有纯虚拟GetID(),用于推断类型而不是使用typeid。在这种情况下,只有1级继承,什么是typeid的成本与虚函数调用的成本?我知道typeid以某种方式使用vtable,但它究竟是如何工作的?

在Linux和Mac上,或使用Itanium C ++ ABI的任何其他内容,typeid(x)编译成两个加载指令 - 它只是从对象的前8个字节加载vptr(即某些vtable的地址) x,然后从该vtable加载-1指针。该指针为&typeid(x)。这是一个函数调用 less 比调用虚方法更贵。

在Windows上,它涉及四个加载指令和几个(可忽略的)ALU操作的顺序,因为Microsoft C ++ ABI是a bit more enterprisey。 (source)老实说,这可能最终与虚拟方法调用相提并论。但与dynamic_cast相比,这仍然很便宜。

A dynamic_cast涉及到C ++运行时的函数调用,它有一个 lot 的加载和条件分支等。

所以是的,利用typeid 比<{1}}更快 。你的用例是否正确? - 这是值得怀疑的。 (参见关于Liskov替代性等的其他答案。)但它会快吗? - 是的。

在这里,我从Vaughn高度评价的答案中获取了玩具基准代码并将其变为an actual benchmark,从而避免了明显的循环提升优化,这种优化使他的所有时间都停滞不前。结果,对于我的Macbook上的libc ++ abi:

dynamic_cast

(低$ g++ test.cc -lbenchmark -std=c++14; ./a.out Run on (4 X 2400 MHz CPU s) 2017-06-27 20:44:12 Benchmark Time CPU Iterations --------------------------------------------------------- bench_dynamic_cast 70407 ns 70355 ns 9712 bench_typeid 31205 ns 31185 ns 21877 bench_id_method 30453 ns 29956 ns 25039 $ g++ test.cc -lbenchmark -std=c++14 -O3; ./a.out Run on (4 X 2400 MHz CPU s) 2017-06-27 20:44:27 Benchmark Time CPU Iterations --------------------------------------------------------- bench_dynamic_cast 57613 ns 57591 ns 11441 bench_typeid 12930 ns 12844 ns 56370 bench_id_method 20942 ns 20585 ns 33965 更好。您可以忽略后两列:&#34; CPU&#34;只是表明它花了所有时间都在运行,没有时间等待,&# 34;迭代&#34;只是为了获得良好的误差而花费的运行次数。)

即使在ns,您也可以看到typeid发生dynamic_cast,但是当您启用优化时,它会更好 - 因为编译器可以优化任何你的代码写。编译器无法对All that ugly code hidden inside libc++abi's __dynamic_cast function进行优化,因此启用-O0并没有多大帮助。

答案 1 :(得分:5)

通常,您不仅要知道类型,还要对该类型的对象执行某些操作。在这种情况下,dynamic_cast更有用:

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
    }
}

我认为这就是为什么你很少看到在实践中使用的typeid。

更新

因为这个问题与性能有关。我在g ++ 4.5.1上运行了一些基准测试。使用此代码:

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;
}

没有优化,我得到了这些运行时:

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

通过优化-O2,我得到了这些运行时:

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

因此,在使用此编译器进行优化时,dynamic_cast似乎是最快的方法。

答案 2 :(得分:2)

在几乎所有情况下,您都不需要确切类型,但是您希望确保它具有给定类型或从中派生的任何类型。如果从它派生的类型的对象不能替换相关类型的对象,那么您违反了Liskov Substitution Principle,这是正确OO设计的最基本规则之一。