Dav*_*vid 10 c++ type-erasure c++11
我正在考虑使用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);
}
}
Run Code Online (Sandbox Code Playgroud)
我从未见过其他人这样做过.替代方案是BaseThing具有纯虚拟GetID(),用于推断类型而不是使用typeid.在这种情况下,只有1级继承,typeid的成本与虚函数调用的成本是多少?我知道typeid以某种方式使用vtable,但它究竟是如何工作的呢?
这是可取的而不是GetID(),因为尝试确保ID是唯一且确定的,需要相当多的hackery .
Quu*_*one 14
替代方案是BaseThing具有纯虚拟
GetID(),用于推断类型而不是使用typeid.在这种情况下,只有1级继承,typeid的成本与虚函数调用的成本是多少?我知道typeid以某种方式使用vtable,但它究竟是如何工作的呢?
在Linux和Mac上,或使用Itanium C++ ABI的任何其他内容,typeid(x)编译成两个加载指令 - 它只是从对象的前8个字节加载vptr(即某些vtable的地址)x,然后-1从中加载th指针那个vtable.那个指针是&typeid(x).这是一个比调用虚方法更便宜的函数调用.
在Windows上,它涉及四个加载指令和几个(可忽略的)ALU操作的顺序,因为Microsoft C++ ABI 更加企业化.(来源)老实说,这可能最终与虚拟方法调用相提并论.但与a相比,这仍然便宜dynamic_cast.
A dynamic_cast涉及到C++运行时的函数调用,它有很多负载和条件分支等.
所以,是的,开发typeid将是非常非常快于dynamic_cast.这将是正确的为您的使用情况- ?这是值得商榷的.(参见关于Liskov可替代性等的其他答案.)但它会快吗? - 是的
在这里,我从Vaughn高度评价的答案中获取了玩具基准代码,并将其作为一个实际的基准,避免了明显的循环提升优化,这些都优化了他的所有时间.结果,对于我的Macbook上的libc ++ abi:
$ 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
Run Code Online (Sandbox Code Playgroud)
(ns越低越好.你可以忽略后两列:"CPU"只是表明它正在花费所有时间运行而没有时间等待,而"迭代"只是为了获得良好的误差而花费的运行次数. )
您甚至可以看到typeidthrashes ,但是当您启用优化时,它甚至会更好 - 因为编译器可以优化您编写的任何代码.隐藏在libc ++ abi 函数中的所有丑陋代码都不能被编译器优化,因此开启时没有多大帮助.dynamic_cast-O0__dynamic_cast-O3
通常,您不仅要知道类型,还要对该类型的对象执行某些操作.在这种情况下,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
}
}
Run Code Online (Sandbox Code Playgroud)
我想这就是为什么你很少看到在实践中使用的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;
}
Run Code Online (Sandbox Code Playgroud)
没有优化,我得到了这些运行时:
test1: 2.277s
test2: 0.629s
test3: 0.469s
Run Code Online (Sandbox Code Playgroud)
通过优化-O2,我得到了这些运行时:
test1: 0.118s
test2: 0.220s
test3: 0.290s
Run Code Online (Sandbox Code Playgroud)
因此,当使用此编译器进行优化时,dynamic_cast似乎是最快的方法.