我正在考虑使用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。
答案 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设计的最基本规则之一。