C++ 类型比较:typeid vs double dispatch dynamic_cast
C++ type comparison: typeid vs double dispatch dynamic_cast
是否有任何性能或稳健性原因使您更喜欢其中之一?
#include <iostream>
#include <typeinfo>
struct B
{
virtual bool IsType(B const * b) const { return IsType2nd(b) && b->IsType2nd(this); }
virtual bool IsType2nd(B const * b) const { return dynamic_cast<decltype(this)>(b) != nullptr; }
};
struct D0 : B
{
virtual bool IsType(B const * b) const { return IsType2nd(b) && b->IsType2nd(this); }
virtual bool IsType2nd(B const * b) const { return dynamic_cast<decltype(this)>(b) != nullptr; }
};
struct D1 : B
{
virtual bool IsType(B const * b) const { return IsType2nd(b) && b->IsType2nd(this); }
virtual bool IsType2nd(B const * b) const { return dynamic_cast<decltype(this)>(b) != nullptr; }
};
int main()
{
using namespace std;
B b, bb;
D0 d0, dd0;
D1 d1, dd1;
cout << "type B == type B : " << (b.IsType(&bb) ? "true " : "false") << endl;
cout << "type B == type D0 : " << (b.IsType(&dd0) ? "true " : "false") << endl;
cout << "type B == type D1 : " << (b.IsType(&dd1) ? "true " : "false") << endl;
cout << "type D0 == type B : " << (d0.IsType(&bb) ? "true " : "false") << endl;
cout << "type D0 == type D0 : " << (d0.IsType(&dd0) ? "true " : "false") << endl;
cout << "type D0 == type D1 : " << (d0.IsType(&dd1) ? "true " : "false") << endl;
cout << "type D1 == type B : " << (d1.IsType(&bb) ? "true " : "false") << endl;
cout << "type D1 == type D0 : " << (d1.IsType(&dd0) ? "true " : "false") << endl;
cout << "type D1 == type D1 : " << (d1.IsType(&dd1) ? "true " : "false") << endl;
cout << endl;
cout << "type B == type B : " << (typeid(b) == typeid(bb) ? "true " : "false") << endl;
cout << "type B == type D0 : " << (typeid(b) == typeid(dd0) ? "true " : "false") << endl;
cout << "type B == type D1 : " << (typeid(b) == typeid(dd1) ? "true " : "false") << endl;
cout << "type D0 == type B : " << (typeid(d0) == typeid(&bb) ? "true " : "false") << endl;
cout << "type D0 == type D0 : " << (typeid(d0) == typeid(dd0) ? "true " : "false") << endl;
cout << "type D0 == type D1 : " << (typeid(d0) == typeid(dd1) ? "true " : "false") << endl;
cout << "type D1 == type B : " << (typeid(d1) == typeid(bb) ? "true " : "false") << endl;
cout << "type D1 == type D0 : " << (typeid(d1) == typeid(dd0) ? "true " : "false") << endl;
cout << "type D1 == type D1 : " << (typeid(d1) == typeid(dd1) ? "true " : "false") << endl;
}
输出:
type B == type B : true
type B == type D0 : false
type B == type D1 : false
type D0 == type B : false
type D0 == type D0 : true
type D0 == type D1 : false
type D1 == type B : false
type D1 == type D0 : false
type D1 == type D1 : true
type B == type B : true
type B == type D0 : false
type B == type D1 : false
type D0 == type B : false
type D0 == type D0 : true
type D0 == type D1 : false
type D1 == type B : false
type D1 == type D0 : false
type D1 == type D1 : true
从设计的角度来看,双重调度要灵活得多:
目前您正在检查 IsType2nd(b) && b->IsType2nd(this)
类型之间的严格相等性。但可能在某个时候你想进一步推导
但有一天您可能想要进一步推导 D1,但在比较类型时仍想将其视为 D1 对象所在的位置。这种特殊情况很容易用双重调度来完成。
这种灵活性是有代价的:汇编代码将使用 2 个通过 vtable 的间接调用,外加一个动态转换。
直接的类型信息并不是最好的设计,正如 Sergey 所指出的:它将始终是严格的类型比较,不可能有特殊情况。
这种不灵活性伴随着代码生成的简单性优势:代码只需在 vtable 的开头获取动态类型信息(并且编译器可以轻松地优化此获取类型已知的对象)编译时间。
出于好奇,这里some code generated:他的typeid在编译时被优化掉了,而double displatch仍然依赖于间接调用。
如评论中所述,它遵循另一种既不使用 typeid
也不依赖 dynamic_cast
.
的可能解决方案
我添加了几个额外的示例来展示如何轻松定义 family 类型(例如,这里 D1
和 D1Bis
看起来属于同一家族类型,即使它们实际上是不同的类型)。
无论如何,不确定这是一个理想的功能...
希望您会感兴趣。
#include<iostream>
struct BB {
virtual unsigned int GetType() = 0;
bool IsType(BB *other) {
return GetType() == other->GetType();
}
protected:
static unsigned int bbType;
};
unsigned int BB::bbType = 0;
struct B: public BB {
unsigned int GetType() override {
static unsigned int bType = BB::bbType++;
return bType;
}
};
struct D0: public B {
unsigned int GetType() override {
static unsigned int d0Type = BB::bbType++;
return d0Type;
}
};
struct D1: public B {
unsigned int GetType() override {
static unsigned int d1Type = BB::bbType++;
return d1Type;
}
};
struct D1Bis: public D1 { };
int main() {
using namespace std;
B b, bb;
D0 d0, dd0;
D1 d1, dd1;
D1Bis d1Bis;
cout << "type B == type B : " << (b.IsType(&bb) ? "true " : "false") << endl;
cout << "type B == type D0 : " << (b.IsType(&dd0) ? "true " : "false") << endl;
cout << "type B == type D1 : " << (b.IsType(&dd1) ? "true " : "false") << endl;
cout << "type B == type D1BIS : " << (b.IsType(&d1Bis) ? "true " : "false") << endl;
cout << "type D0 == type B : " << (d0.IsType(&bb) ? "true " : "false") << endl;
cout << "type D0 == type D0 : " << (d0.IsType(&dd0) ? "true " : "false") << endl;
cout << "type D0 == type D1 : " << (d0.IsType(&dd1) ? "true " : "false") << endl;
cout << "type D0 == type D1BIS : " << (d0.IsType(&d1Bis) ? "true " : "false") << endl;
cout << "type D1 == type B : " << (d1.IsType(&bb) ? "true " : "false") << endl;
cout << "type D1 == type D0 : " << (d1.IsType(&dd0) ? "true " : "false") << endl;
cout << "type D1 == type D1 : " << (d1.IsType(&dd1) ? "true " : "false") << endl;
cout << "type D1 == type D1Bis : " << (d1.IsType(&d1Bis) ? "true " : "false") << endl;
}
是否有任何性能或稳健性原因使您更喜欢其中之一?
#include <iostream>
#include <typeinfo>
struct B
{
virtual bool IsType(B const * b) const { return IsType2nd(b) && b->IsType2nd(this); }
virtual bool IsType2nd(B const * b) const { return dynamic_cast<decltype(this)>(b) != nullptr; }
};
struct D0 : B
{
virtual bool IsType(B const * b) const { return IsType2nd(b) && b->IsType2nd(this); }
virtual bool IsType2nd(B const * b) const { return dynamic_cast<decltype(this)>(b) != nullptr; }
};
struct D1 : B
{
virtual bool IsType(B const * b) const { return IsType2nd(b) && b->IsType2nd(this); }
virtual bool IsType2nd(B const * b) const { return dynamic_cast<decltype(this)>(b) != nullptr; }
};
int main()
{
using namespace std;
B b, bb;
D0 d0, dd0;
D1 d1, dd1;
cout << "type B == type B : " << (b.IsType(&bb) ? "true " : "false") << endl;
cout << "type B == type D0 : " << (b.IsType(&dd0) ? "true " : "false") << endl;
cout << "type B == type D1 : " << (b.IsType(&dd1) ? "true " : "false") << endl;
cout << "type D0 == type B : " << (d0.IsType(&bb) ? "true " : "false") << endl;
cout << "type D0 == type D0 : " << (d0.IsType(&dd0) ? "true " : "false") << endl;
cout << "type D0 == type D1 : " << (d0.IsType(&dd1) ? "true " : "false") << endl;
cout << "type D1 == type B : " << (d1.IsType(&bb) ? "true " : "false") << endl;
cout << "type D1 == type D0 : " << (d1.IsType(&dd0) ? "true " : "false") << endl;
cout << "type D1 == type D1 : " << (d1.IsType(&dd1) ? "true " : "false") << endl;
cout << endl;
cout << "type B == type B : " << (typeid(b) == typeid(bb) ? "true " : "false") << endl;
cout << "type B == type D0 : " << (typeid(b) == typeid(dd0) ? "true " : "false") << endl;
cout << "type B == type D1 : " << (typeid(b) == typeid(dd1) ? "true " : "false") << endl;
cout << "type D0 == type B : " << (typeid(d0) == typeid(&bb) ? "true " : "false") << endl;
cout << "type D0 == type D0 : " << (typeid(d0) == typeid(dd0) ? "true " : "false") << endl;
cout << "type D0 == type D1 : " << (typeid(d0) == typeid(dd1) ? "true " : "false") << endl;
cout << "type D1 == type B : " << (typeid(d1) == typeid(bb) ? "true " : "false") << endl;
cout << "type D1 == type D0 : " << (typeid(d1) == typeid(dd0) ? "true " : "false") << endl;
cout << "type D1 == type D1 : " << (typeid(d1) == typeid(dd1) ? "true " : "false") << endl;
}
输出:
type B == type B : true
type B == type D0 : false
type B == type D1 : false
type D0 == type B : false
type D0 == type D0 : true
type D0 == type D1 : false
type D1 == type B : false
type D1 == type D0 : false
type D1 == type D1 : true
type B == type B : true
type B == type D0 : false
type B == type D1 : false
type D0 == type B : false
type D0 == type D0 : true
type D0 == type D1 : false
type D1 == type B : false
type D1 == type D0 : false
type D1 == type D1 : true
从设计的角度来看,双重调度要灵活得多:
目前您正在检查
IsType2nd(b) && b->IsType2nd(this)
类型之间的严格相等性。但可能在某个时候你想进一步推导但有一天您可能想要进一步推导 D1,但在比较类型时仍想将其视为 D1 对象所在的位置。这种特殊情况很容易用双重调度来完成。
这种灵活性是有代价的:汇编代码将使用 2 个通过 vtable 的间接调用,外加一个动态转换。
直接的类型信息并不是最好的设计,正如 Sergey 所指出的:它将始终是严格的类型比较,不可能有特殊情况。
这种不灵活性伴随着代码生成的简单性优势:代码只需在 vtable 的开头获取动态类型信息(并且编译器可以轻松地优化此获取类型已知的对象)编译时间。
出于好奇,这里some code generated:他的typeid在编译时被优化掉了,而double displatch仍然依赖于间接调用。
如评论中所述,它遵循另一种既不使用 typeid
也不依赖 dynamic_cast
.
我添加了几个额外的示例来展示如何轻松定义 family 类型(例如,这里 D1
和 D1Bis
看起来属于同一家族类型,即使它们实际上是不同的类型)。
无论如何,不确定这是一个理想的功能...
希望您会感兴趣。
#include<iostream>
struct BB {
virtual unsigned int GetType() = 0;
bool IsType(BB *other) {
return GetType() == other->GetType();
}
protected:
static unsigned int bbType;
};
unsigned int BB::bbType = 0;
struct B: public BB {
unsigned int GetType() override {
static unsigned int bType = BB::bbType++;
return bType;
}
};
struct D0: public B {
unsigned int GetType() override {
static unsigned int d0Type = BB::bbType++;
return d0Type;
}
};
struct D1: public B {
unsigned int GetType() override {
static unsigned int d1Type = BB::bbType++;
return d1Type;
}
};
struct D1Bis: public D1 { };
int main() {
using namespace std;
B b, bb;
D0 d0, dd0;
D1 d1, dd1;
D1Bis d1Bis;
cout << "type B == type B : " << (b.IsType(&bb) ? "true " : "false") << endl;
cout << "type B == type D0 : " << (b.IsType(&dd0) ? "true " : "false") << endl;
cout << "type B == type D1 : " << (b.IsType(&dd1) ? "true " : "false") << endl;
cout << "type B == type D1BIS : " << (b.IsType(&d1Bis) ? "true " : "false") << endl;
cout << "type D0 == type B : " << (d0.IsType(&bb) ? "true " : "false") << endl;
cout << "type D0 == type D0 : " << (d0.IsType(&dd0) ? "true " : "false") << endl;
cout << "type D0 == type D1 : " << (d0.IsType(&dd1) ? "true " : "false") << endl;
cout << "type D0 == type D1BIS : " << (d0.IsType(&d1Bis) ? "true " : "false") << endl;
cout << "type D1 == type B : " << (d1.IsType(&bb) ? "true " : "false") << endl;
cout << "type D1 == type D0 : " << (d1.IsType(&dd0) ? "true " : "false") << endl;
cout << "type D1 == type D1 : " << (d1.IsType(&dd1) ? "true " : "false") << endl;
cout << "type D1 == type D1Bis : " << (d1.IsType(&d1Bis) ? "true " : "false") << endl;
}