绕过遗留代码的RTTI

时间:2014-08-25 23:15:55

标签: c++ casting rtti

我一直在寻找一种方法来解决动态类型检查的缓慢问题。在你开始说我应该重新设计所有内容之前,让我告诉你,设计是在5年前决定的。我无法修复之后的所有400,000行代码(我希望可以),但我可以进行一些更改。我在类型识别上运行了这个小测试:

#include <iostream>
#include <typeinfo>
#include <stdint.h>
#include <ctime>

using namespace std;

#define ADD_TYPE_ID \
    static intptr_t type() { return reinterpret_cast<intptr_t>(&type); }\
    virtual intptr_t getType() { return type(); }

struct Base
{
    ADD_TYPE_ID;
};

template <typename T>
struct Derived : public Base
{
    ADD_TYPE_ID;
};

int main()
{
    Base* b = new Derived<int>();
    cout << "Correct Type: " << (b->getType() == Derived<int>::type()) << endl; // true
    cout << "Template Type: " << (b->getType() == Derived<float>::type()) << endl; // false
    cout << "Base Type: " << (b->getType() == Base::type()) << endl; // false

    clock_t begin = clock();
    {
        for (size_t i = 0; i < 100000000; i++)
        {
            if (b->getType() == Derived<int>::type())
                Derived <int>* d = static_cast<Derived<int>*> (b);
        }
    }
    clock_t end = clock();
    double elapsed = double(end - begin) / CLOCKS_PER_SEC;

    cout << "Type elapsed: " << elapsed << endl;

    begin = clock();
    {
        for (size_t i = 0; i < 100000000; i++)
        {
            Derived<int>* d = dynamic_cast<Derived<int>*>(b);
            if (d);
        }
    }
    end = clock();
    elapsed = double(end - begin) / CLOCKS_PER_SEC;

    cout << "Type elapsed: " << elapsed << endl;

    begin = clock();
    {
        for (size_t i = 0; i < 100000000; i++)
        {
            Derived<int>* d = dynamic_cast<Derived<int>*>(b);
            if ( typeid(d) == typeid(Derived<int>*) )
                static_cast<Derived<int>*> (b);
        }
    }
    end = clock();
    elapsed = double(end - begin) / CLOCKS_PER_SEC;

    cout << "Type elapsed: " << elapsed << endl;

   return 0;
}

似乎使用类id(上面第一次解决方案)将是在运行时进行类型检查的最快方法。 这会导致线程出现问题吗?有没有更好的方法在运行时检查类型(没有太多的重新分解)?

编辑我可能还要补充一点,这需要与TI编译器配合使用,目前只支持'03

5 个答案:

答案 0 :(得分:3)

首先请注意,dynamic_cast和RTTI之间存在很大差异:演员告诉您是否可以将基础对象视为某些进一步派生,但不一定是派生的宾语。 RTTI告诉您精确的派生类型。当然,前者更强大,更昂贵。

那么,如果您具有多态层次结构,则可以通过两种自然方式在类型上进行选择。他们是不同的;使用实际适用的那个。

void method1(Base * p)
{
    if (Derived * q = dynamic_cast<Derived *>(p))
    {
        // use q
    }
}

void method2(Base * p)
{
    if (typeid(*p) == typeid(Derived))
    {
        auto * q = static_cast<Derived *>(p);

        // use q
    }
}

另请注意,如果基类是虚拟基础,则通常不提供方法2。如果您的类不是多态的,那么这两种方法都不适用。

在快速测试中,我发现方法2明显快于基于手动ID的解决方案,后者反过来比动态强制解决方案(方法1)快。

答案 1 :(得分:1)

如何比较类的虚函数表?

快速而肮脏的概念证明:

void* instance_vtbl(void* c)
{
    return *(void**)c;
}

template<typename C>
void* class_vtbl()
{
    static C c;
    return instance_vtbl(&c);
}

// ...

begin = clock();
{
    for (size_t i = 0; i < 100000000; i++)
    {
        if (instance_vtbl(b) == class_vtbl<Derived<int>>())
            Derived <int>* d = static_cast<Derived<int>*> (b);
    }
}
end = clock();
elapsed = double(end - begin) / CLOCKS_PER_SEC;

cout << "Type elapsed: " << elapsed << endl;

使用Visual C ++的/Ox切换,这比type / getType技巧快3倍。

答案 2 :(得分:0)

鉴于此类代码

class A {
};

class B : public A {
}

A * a;
B * b = dynamic_cast<B*> (a);
if( b != 0 ) // do something B specific

多态(对吗?)修复它的方法是这样的

class A {
public:
    virtual void specific() { /* do nothing */ }
};

class B : public A {
public:
    virtual void specific() { /* do something B specific */ }
}

A * a;
if( a != 0 ) a->specific();

答案 3 :(得分:0)

当MSVC 2005首次推出时,dynamic_cast&lt;&gt;对于64位代码比32位代码要慢得多。我们想要一个快速简单的解决方案。这就是我们的代码。它可能违反了各种好的设计规则,但转换为删除dynamic_cast&lt;&gt;可以使用脚本自动完成。

class dbbMsgEph {
public:
    virtual dbbResultEph *              CastResultEph() { return 0; }
    virtual const dbbResultEph *        CastResultEph() const { return 0; }
};

class dbbResultEph : public dbbMsgEph {
public:
    virtual dbbResultEph *              CastResultEph() { return this; }
    virtual const dbbResultEph *        CastResultEph() const { return this; }
    static dbbResultEph *               Cast( dbbMsgEph * );
    static const dbbResultEph *         Cast( const dbbMsgEph * );
};

dbbResultEph *
dbbResultEph::Cast( dbbMsgEph * arg )
{
    if( arg == 0 ) return 0;
    return arg->CastResultEph();
}

const dbbResultEph *
dbbResultEph::Cast( const dbbMsgEph * arg )
{
    if( arg == 0 ) return 0;
    return arg->CastResultEph();
}

我们曾经有过

dbbMsgEph * pMsg;
dbbResultEph * pResult = dynamic_cast<dbbResultEph *> (pMsg);

我们将其更改为

dbbResultEph * pResult = dbbResultEph::Cast (pMsg);

使用简单的sed(1)脚本。虚拟函数调用非常有效。

答案 4 :(得分:0)

//在发布模块(VS2008)中这是真的:

cout << "Base Type: " << (b->getType() == Base::type()) << endl;

我想这是因为优化。所以我改变了Derived :: type()的实现

template <typename T>
struct Derived : public Base
{
    static intptr_t type() 
    { 
        cout << "different type()" << endl;
        return reinterpret_cast<intptr_t>(&type); 
    }
    virtual intptr_t getType() { return type(); }
};

然后它有所不同。如果使用这种方法怎么处理呢???