有比使用dynamic_cast更快的方法在运行时检测对象类型吗?

6

我有一个类型层次结构 - GenericClass和许多派生类,包括InterestingDerivedClass,GenericClass是多态的。这里有一个接口

interface ICallback {
    virtual void DoStuff( GenericClass* ) = 0;
};

我需要实现的功能是检测当传递给ICallback::DoStuff()的指向GenericClass*的指针实际上是指向InterestingDerivedClass的指针。
class CallbackImpl : public ICallback {
    void DoStuff( GenericClass* param ) {
        if( dynamic_cast<InterestingDerivedClass*>( param ) != 0 ) {
            return; //nothing to do here
        }
        //do generic stuff
    }
}

GenericClass和派生类不受我的控制,我只能控制CallbackImpl。我对dynamic_cast语句进行了计时 - 这需要大约1400个周期,目前来说是可以接受的,但看起来并不是很快。我试着在调试器中阅读执行dynamic_cast期间所执行的反汇编代码,并发现它执行了很多指令。由于我真的不需要指向派生类的指针,所以有没有一种更快的方法只使用RTTI在运行时检测对象类型?也许有一些特定于实现的方法只检查“is a”关系而不检索指针?

类似于https://dev59.com/aHRB5IYBdhLWcg3wz6ed - Yuval Adam
8个回答

12

我总是认为使用 dynamic_cast 是一种代码异味。你可以在所有情况下都用多态行为来代替它,并提高你的代码质量。在你的示例中,我会做出以下改进:

class GenericClass
{
  virtual void DoStuff()
  {
    // do interesting stuff here
  }
};

class InterestingDerivedClass : public GenericClass
{
  void DoStuff()
  {
    // do nothing
  }
};

class CallbackImpl : public ICallback {
    void DoStuff( GenericClass* param ) {
        param->DoStuff();
    }
}

在您的情况下,您不能修改目标类,您是按照GenericClass类型声明所隐含的契约进行编程。因此,很可能没有任何比dynamic_cast更快的方法可用,因为其他任何方法都需要修改客户端代码。


5
正如其他人所说,使用虚函数是好的实践。还有一个使用它的原因,在你的情况下不适用,但我认为仍然值得一提——它可能比使用动态转换快得多。在我用g++进行的一些(不是非常严格的)测试中,虚函数的性能比dynamic_cast高了4倍。
由于存在这样的差异,也许值得创建自己的继承层次结构来包装您无法控制的代码。您将使用dynamic_cast(一次)创建包装器的实例以决定要创建的派生类型,然后从那里使用虚函数。

2

对我来说,这看起来像是一种相当hackish的设计。 (正如其他人提到的那样,需要使用 dynamic_cast 通常意味着你有一个设计问题). 但如果大部分代码都不受你的控制, 我想你也没有太多可以做的。

然而,我所知道的唯一通用解决方案就是使用 dynamic_cast,而 typeid 只匹配最终派生类型,这在你的情况下可能会起作用。

顺便说一下,dynamic_cast 如此昂贵的原因可能是它必须遍历整个类层次结构。如果你有一个深层次结构,那么这就变得昂贵了(换句话说,总体上不要有一个深层次结构,特别是在C ++中)。

(当然,在第一次执行转换时,大多数类描述符查找可能会缓存未命中,这可能会使你的基准测试结果失真,让其看起来比实际更昂贵)


1
在您的具体用例中,答案是使用虚函数。
然而,在某些情况下,您需要动态地进行向下转型。有一些技术可以使此操作更快(或者取决于您的编译器实现dynamic_cast的智能程度而更快),特别是如果您限制自己只使用单继承。主要思想是,如果您以某种方式知道确切的类型,则static_cast会更快:
f(A* pA)
{
  if (isInstanceOfB(pA))
  {
    B* pB = static_cast<B*>(pA);
    // do B stuff...
  }
}

当然,问题现在是提供一个快速实现isInstanceOfB()的方法。

例如,请参见boost::type_traits


1

标准的dynamic_cast非常灵活,但通常非常慢,因为它处理了许多你可能不感兴趣的边角情况。如果你使用单一继承,可以用基于虚函数的简单实现来替换它。

示例实现:

// fast dynamic cast
//! Fast dynamic cast declaration
/*!
Place USE_CASTING to class that should be recnognized by dynamic casting.
Do not forget do use DEFINE_CASTING near class definition.
*\note Function dyn_cast is fast and robust when used correctly.
Each class that should be used as target for dyn_cast
must use USE_CASTING and DEFINE_CASTING macros.\n
Forgetting to do so may lead to incorrect program execution,
because class may be sharing _classId with its parent and IsClassId
will return true for both parent and derived class, making impossible'
to distinguish between them.
*/
#define USE_CASTING(baseType) \
  public: \
  static int _classId; \
  virtual size_t dyn_sizeof() const {return sizeof(*this);} \
  bool IsClassId( const int *t ) const \
  { \
    if( &_classId==t ) return true; \
    return baseType::IsClassId(t); \
  }

//! Fast dynamic cast root declaration
/*!
Place USE_CASTING_ROOT to class that should act as
root of dynamic casting hierarchy
*/

#define USE_CASTING_ROOT \
  public: \
  static int _classId; \
  virtual size_t dyn_sizeof() const {return sizeof(*this);} \
  virtual bool IsClassId( const int *t ) const { return ( &_classId==t ); }

//! Fast dynamic cast definition
#define DEFINE_CASTING(Type) \
  int Type::_classId;

template <class To,class From>
To *dyn_cast( From *from )
{
  if( !from ) return NULL;
  if( from->IsClassId(&To::_classId) )
  {
    assert(dynamic_cast<To *>(from));
    return static_cast<To *>(from);
  }
  return NULL;
}

话虽如此,我完全同意其他人的观点,dynamic_cast是有问题的,而且你往往会用更简洁的方式实现相同的目标。 话虽如此,与goto类似,有些情况下它确实非常有用且易读。

另一个注意点:如果你说问题中涉及到的类不在你的控制范围内,那么这个解决方案对你没有用,因为它要求你修改这些类(不多,只需添加几行代码,但你需要修改它们)。 如果情况确实如此,你需要使用语言提供的dynamic_cast和typeinfo。


1

比较 type_info 是否会更快?(在参数 param 上调用 typeid


2
是的,它更快,但它不测试继承,只有完全匹配。 - Macke
1
可能会更快,但这不是一个好主意(就像上面的原始代码一样)。 - Martin York
我认为他已经在其他答案中受到了足够的讲解。他要求以更快的方式完成他正在做的事情。(比较type_infos只会捕获确切地InterestingDerivedClass,而不是任何派生类)。 - James Hopkin
比较 type_info 的时间需要 600 到 1200 个周期,形式上更快,但并不令人印象深刻。再加上我不得不进行整个调查,找出编译器希望为成功比较使用的类型,并且它只适用于精确匹配。与此同时,dynamic_cast 可以直接工作,并准确检查“is a”关系。 - sharptooth

1
首先,不要过早进行优化。其次,如果你在内部查询一个对象的具体实现,很可能你的设计有问题(考虑双重分派)。
至于原始问题,引入一个名为GetRuntimeType()的函数到ICallback中会非常好:可以参考MFC来了解如何实现这一点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接