C ++快速动态类型/子类型检查

时间:2013-09-11 00:57:27

标签: c++ rtti typechecking

正如标题所示,我正在寻找一种快速的运行时类型检查方法。为了说明我的问题,假设你有一个类层次结构,如下所示:

      Base
     /     \
    A       D
   / \     / \
  C   B   F   E
       \ /
        G

我的程序将单个列表中任何类的所有实例都保存为Base_ptr,因为所有这些类共享常见任务。现在,某些派生类需要知道另一个类的实例是否存在。到目前为止一切顺利,我知道dynamic_cast和typeid() - 运算符,但两者都有一些市长缺点:

    如果类型不兼容(例如尝试将E的实例转换为C),
  1. dynamic_cast会消耗大量处理时间。
  2. typeid()在“isTypeOrSubtype”-cases中不起作用,例如你需要D的所有实例或从D派生(所以Es,Fs和Gs也是如此)
  3. 如果此测试成功返回,理想的解决方案将是某种“isTypeOrSubtype” - 测试并且仅进行转换。我有一个自己的方法与一些宏定义和预先计算的类名哈希,但它是非常丑陋和难以维护。因此,我正在寻找一种更清晰,更快速的动态类型和子类型检查方法,每秒可检查超过2000万次。

4 个答案:

答案 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 techniqueusing dynamic cast快20至50倍。 1 该实现使用宏来允许将新类添加到单个位置,在此之后,以自动方式对基类进行适当的扩展。

  

(1) - 我最初的时钟速度接近100倍,但这并没有我添加的isTypeOrSubtype()包装器方法来模拟所需的接口。

如果灵活性具有比性能更高的值,那么性能稍差的解决方案是使用map来关联类型和相应的指针值(使指针值不需要动态转换)。 map实例在基类中维护,关联由子类的构造函数创建。是否使用常规mapunordered_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 suggestionusing dynamic cast快10到30倍。我不认为IDEONE会进行优化编译,因此我希望时间更接近生产构建的第一个建议。实现的机制使用typeid(...).name()作为地图的关键。 2

  

(2) - 这假设typeid(...).name()返回类似于字符串文字的内容,并且在操作相同类型时始终返回相同的字符串指针。如果您的系统没有这种方式,您可以修改map以取代std::string作为密钥,但性能会降低。

答案 1 :(得分:0)

我写了一个自己问题的答案,因为这是一种避免RTTI的不同方法,但对动态类型/子类型检查的快速方法没有真正的答案。 这仍然不是一个干净的解决方案,但我能想到的最好的解决方案。

如果此层次结构中的每个类都具有以下特征,我可以跳过大部分RTTI。

  1. 每个类都应该有一个私有成员:static SecureVector<[class]*> s_Instances;其中SecureVector<T>是一个线程安全的向量。
  2. 在每个构造函数的末尾,应该调用s_Instances.push_back(this);,以跟踪新创建的该类的实例
  3. 在析构函数的开头,应调用s_Instances.erase(this);,以删除此实例引用
  4. 每个类都应该有一个公共函数:static const SecureVector<[class]*>& Instances() { return s_Instances; }来获取一个包含所有这个或任何派生类的实例的不可修改的向量
  5. 这样做,每次调用构造函数时,实例都会将自身添加到自己的实例列表中。当派生类调用它们的超级构造函数时,超类将其自身添加到其各自的实例列表中。 例如。如果我在上面的层次结构中随机创建100个实例,那么我的BaseInstances()向量中总共会有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* TypeLoctypedef 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
}

这也适用于更具体的检查。所以你可以检查你想要的任何类型。