正如标题所示,我正在寻找一种快速的运行时类型检查方法。为了说明我的问题,假设你有一个类层次结构,如下所示:
Base
/ \
A D
/ \ / \
C B F E
\ /
G
我的程序将单个列表中任何类的所有实例都保存为Base_ptr,因为所有这些类共享常见任务。现在,某些派生类需要知道另一个类的实例是否存在。到目前为止一切顺利,我知道dynamic_cast和typeid() - 运算符,但两者都有一些市长缺点:
如果此测试成功返回,理想的解决方案将是某种“isTypeOrSubtype” - 测试并且仅进行转换。我有一个自己的方法与一些宏定义和预先计算的类名哈希,但它是非常丑陋和难以维护。因此,我正在寻找一种更清晰,更快速的动态类型和子类型检查方法,每秒可检查超过2000万次。
答案 0 :(得分:0)
如果您的程序知道将要测试的所有子类型,您可以使用返回指向子类型的指针的虚拟接口。正如downvotes和comments所指出的,这不是最灵活的方法,因为它要求基类知道所有派生类。但是,它非常快。因此,对性能的灵活性进行权衡。
class Base {
//...
virtual A * is_A () { return 0; }
virtual B * is_B () { return 0; }
//...
template <typename MAYBE_DERIVED>
MAYBE_DERIVED * isTypeOrSubtype () {
//...dispatch to template specialization to call is_X()
}
};
//...
class A : virtual public Base {
//...
A * is_A () { return this; }
};
在IDEONE上,suggested technique比using dynamic cast快20至50倍。 1 该实现使用宏来允许将新类添加到单个位置,在此之后,以自动方式对基类进行适当的扩展。
(1) - 我最初的时钟速度接近100倍,但这并没有我添加的
isTypeOrSubtype()
包装器方法来模拟所需的接口。
如果灵活性具有比性能更高的值,那么性能稍差的解决方案是使用map
来关联类型和相应的指针值(使指针值不需要动态转换)。 map
实例在基类中维护,关联由子类的构造函数创建。是否使用常规map
或unordered_map
将取决于有多少子类实际上继承了基类。我认为数字会很小,所以常规的map
就足够了。
class Base {
std::map<const char *, void *> children_;
//...
template <typename MAYBE_DERIVED>
MAYBE_DERIVED * isTypeOrSubtype () {
auto x = children_.find(typeid(MAYBE_DERIVED).name());
return ((x != children_.end())
? static_cast<MAYBE_DERIVED *>(x->second)
: 0);
}
};
//...
class A : virtual public Base {
//...
A () { children_[typeid(A).name()] = this; }
//...
};
在IDEONE上,此second suggestion比using dynamic cast快10到30倍。我不认为IDEONE会进行优化编译,因此我希望时间更接近生产构建的第一个建议。实现的机制使用typeid(...).name()
作为地图的关键。 2
(2) - 这假设
typeid(...).name()
返回类似于字符串文字的内容,并且在操作相同类型时始终返回相同的字符串指针。如果您的系统没有这种方式,您可以修改map
以取代std::string
作为密钥,但性能会降低。
答案 1 :(得分:0)
我写了一个自己问题的答案,因为这是一种避免RTTI的不同方法,但对动态类型/子类型检查的快速方法没有真正的答案。 这仍然不是一个干净的解决方案,但我能想到的最好的解决方案。
如果此层次结构中的每个类都具有以下特征,我可以跳过大部分RTTI。
static SecureVector<[class]*> s_Instances;
其中SecureVector<T>
是一个线程安全的向量。 s_Instances.push_back(this);
,以跟踪新创建的该类的实例s_Instances.erase(this);
,以删除此实例引用static const SecureVector<[class]*>& Instances() { return s_Instances; }
来获取一个包含所有这个或任何派生类的实例的不可修改的向量这样做,每次调用构造函数时,实例都会将自身添加到自己的实例列表中。当派生类调用它们的超级构造函数时,超类将其自身添加到其各自的实例列表中。
例如。如果我在上面的层次结构中随机创建100个实例,那么我的Base
类Instances()
向量中总共会有100个条目。
在代码中,这将是这样的:
class Base
{
static SecureVector<Base*> s_Instances; // 1. condition
public:
Base()
{
s_Instances.push_back(this); // 2. condition
}
~Base()
{
s_Instances.erase(this); // 3. condition
}
static const SecureVector<Base*>& Instances() { return s_Instances; } // 4. condition
};
这仍然只是一种解决方法,因为必须手动添加四个条件(或通过宏或类似的东西)。
答案 2 :(得分:0)
前段时间我使用过这样的东西:
// the actual type is irrelevant, const char*, int, ...
// but const char* is great for debugging, when it contains the actual class name
typedef const char* TypeId;
class Base {
// actually the type id is not the value, but its memory location !
// the value is irrelevant (but its a good idea to set it to the class name)
static TypeId s_myTypeId;
public:
static TypeId* getClassType() { return &s_myTypeId; }
virtual TypeId* getInstanceType() { return &s_myTypeId; }
static TypeId* getClassBaseType() { return NULL; }
virtual TypeId* getInstanceBaseType() { return NULL; }
virtual bool isType( TypeId* type ) { return type==getInstanceType(); }
virtual bool isTypeOrSubType( TypeId* type ) { return isType(type); }
};
template< class MyBase >
class TBase : public MyBase {
// actually the type id is not the value, but its memory location !
// the value is irrelevant (but its a good idea to set it to the class name)
static TypeId s_myTypeId;
public:
static TypeId* getClassType() { return &s_myTypeId; }
virtual TypeId* getInstanceType() { return &s_myTypeId; }
static TypeId* getClassBaseType() { return MyBase::getClassType(); }
virtual TypeId* getInstanceBaseType() { return MyBase::getInstanceType(); }
virtual bool isType( TypeId* type ) { return type==getInstanceType(); }
virtual bool isTypeOrSubType( TypeId* type ) { return isType(type) || MyBase::isTypeOrSubType(type); }
};
// by deriving from TBase<Base>, a new instantiation of s_myTypeId was created,
// so the class now has its very own unique type id,
// and it inherited all the type resolution magic
class A : public TBase<Base> {
};
// NOTE: class B must not derive directly from A, but from TBase<A>
// imagine a hidden class between B and A,
// actually B inherits from the TBase<A> instantiation, which in turn inherits from A
class B : public TBase<A> {
};
// you will also need to instantiate the static members
// hereby the memory location will be reserved,
// and on execution that memory location becomes the unique type id
#define IMPLEMENT_RTTI(CL) TypeId CL::s_myTypeId = STRFY(CL)
// one per class per source file:
IMPLEMENT_RTTI(Base);
IMPLEMENT_RTTI(A);
IMPLEMENT_RTTI(B);
// example usage:
A a;
B b;
b.getInstanceType()==B::getClassType(); // TRUE
b.getInstanceBaseType()==A::getClassType(); // TRUE
B::getClassBaseType()==A::getClassType(); // TRUE
b.isType( B::getClassType() ); // TRUE
b.isType( A::getClassType() ); // FALSE
b.isTypeOrSubType( B::getClassType() ); // TRUE
b.isTypeOrSubType( A::getClassType() ); // TRUE
b.isTypeOrSubType( Base::getClassType() ); // TRUE
安全,快速且易于使用。你必须遵守两条规则:
class X
继承,而是从TBase<X>
继承,IMPLEMENT_RTTI(Me)
。有一个缺点:它还不支持多重继承。但是可以通过一些改变来实现。
可能TypeId
类型应该像typedef const char* TypeLoc
和typedef TypeLoc* TypeId
一样组成。也许只是一个品味问题。
答案 3 :(得分:-1)
dynamic_cast
可以为此付出奇妙的效果!
Base *instance = //get the pointer from your collection;
A* ap = dynamic_cast<A*>(instance);
D* dp = dynamic_cast<D*>(instance);
if(ap) {
//instance is an A or a subclass of A
}
if(dp) {
//instance is a D or a subclass of D
}
这也适用于更具体的检查。所以你可以检查你想要的任何类型。