dynamic_cast的正确用例是什么?

时间:2011-04-25 08:53:54

标签: c++ inheritance types dynamic-cast

我多次被告知(并且在实践中看到自己)使用dynamic_cast通常意味着糟糕的设计,因为它可以而且应该被虚拟函数替换。

例如,请考虑以下代码:

class Base{...};
class Derived:public Base{...};
...
Base* createSomeObject(); // Might create a Derived object
...
Base* obj = createSomeObject();
if(dynamic_cast<Derived*>(obj)){
 // do stuff in one way
}
else{
// do stuff in some other way
}

可以很容易地看出,我们只需要向doStuff()添加虚拟函数Base,而不是编写动态强制转换,然后在Derived中重新实现它。

在这种情况下,我的问题是,为什么我们在语言中都有dynamic_cast?有没有一个例子,其中使用dynamic_cast是合理的?

5 个答案:

答案 0 :(得分:26)

虚函数的问题在于层次结构中的所有类必须具有实现或是抽象的,并且这绝对不是正确的事情。例如,如果Base是接口,那么在if中,您需要访问Derived的内部实现细节吗?这在虚拟功能中肯定是行不通的。此外,在某些多重继承情况下,向上转发和向下转换都需要dynamic_cast。在虚拟功能中可以做些什么是有限制的 - 例如,模板。最后,有时您需要存储Derived*,而不仅仅是调用函数。

基本上,虚拟函数只能在某些个案例中使用,而不是所有

答案 1 :(得分:17)

我认为有两种情况使用dynamic_cast是有效的事情。第一个是检查对象是否支持接口,第二个是打破封装。让我详细解释一下。

检查界面

考虑以下功能:

void DoStuffToObject( Object * obj )
{
    ITransactionControl * transaction = dynamic_cast<ITransactionControl>( obj );

    if( transaction )
        transaction->Begin();

    obj->DoStuff();

    if( transaction )
        transaction->Commit();
}

(ITransactionControl将是一个纯抽象类。)在此函数中,如果对象支持事务语义,我们希望在事务上下文中使用“DoStuff”。如果没有,那么无论如何都可以继续。

现在我们当然可以将虚拟的Begin()和Commit()方法添加到Object类中,但是派生自Object的每个类都会获得Begin()和Commit()方法,即使他们没有意识到交易。在这种情况下,在基类中使用虚拟方法只会污染其接口。上面的例子促进了对单一责任原则和界面隔离原则的更好的遵守。

打破封装

考虑到dynamic_cast通常被认为是有害的,这似乎是一个奇怪的建议,因为它允许你打破封装。但是,如果操作正确,这可能是一种非常安全和强大的技术。考虑以下功能:

std::vector<int> CopyElements( IIterator * iterator )
{
   std::vector<int> result;

   while( iterator->MoveNext() )
       result.push_back( iterator->GetCurrent() );

   return result;
}

这里没有错。但现在假设您开始在现场看到性能问题。经过分析,您会发现您的程序在此功能中花费了大量时间。 push_backs导致多个内存分配。更糟糕的是,事实证明“迭代器”几乎总是“ArrayIterator”。如果只有你能够做出这样的假设,那么你的表现问题就会消失。使用dynamic_cast,您可以做到这一点:

 std::vector<int> CopyElements( IIterator * iterator )
{
   ArrayIterator * arrayIterator = dynamic_cast<ArrayIterator *>( iterator );

   if( arrayIterator ) {
       return std::vector<int>( arrayIterator->Begin(), arrayIterator->End() );
   } else {
       std::vector<int> result;

       while( iterator->MoveNext() )
           result.push_back( iterator->GetCurrent() );

       return result;
   }
}

再一次,我们可以在IIterator类中添加一个虚拟的“CopyElements”方法,但这也有我上面提到的相同缺点。也就是说,它使界面膨胀。它强制所有实现者都有一个CopyElements方法,即使ArrayIterator是唯一会在其中做一些有趣事情的类。

所有这一切,我建议谨慎使用这些技术。 dynamic_cast不是免费的,可以滥用。 (坦率地说,我看到它的使用频率远远超过我看到它使用得很好。)如果你发现自己经常使用它,那么考虑其他方法是个好主意。

答案 2 :(得分:6)

  

可以很容易地看出,我们只需要向Base添加一个虚函数doStuff(),然后在Derived中重新实现它,而不是编写动态强制转换。

YES。这就是virtual函数的用途。

class Base
{
  public:
      virtual void doStuff();
};
class Derived: public Base
{
  public:
      virtual void doStuff(); //override base implementation
};

Base* createSomeObject(); // Might create a Derived object

Base* obj = createSomeObject();
obj->doStuff(); //might call Base::doStuff() or Derived::doStuff(), depending on the dynamic type of obj;

您是否注意到virtual函数如何消除dynamic_cast

使用dynamic_cast通常表示您无法使用通用界面(即虚拟函数)实现目标,因此您需要将其强制转换为精确类型,以便调用特定的类型基类/派生类的成员函数。

答案 3 :(得分:0)

子类可能具有基类中不存在的其他方法,并且在其他子类的上下文中可能没有意义。但一般来说你应该避免它。

答案 4 :(得分:0)

如果您有一个接收BaseClass *的方法(称之为foo),并且它已用于DerivedClass *,那该怎么办? 如果我写的话:

BaseClass* x = new DerivedClass();

并用x调用foo,我将得到foo(BaseClass varName),而不是foo(DerivedClass varName)。

一种解决方案是使用dynamic_cast并针对NULL测试它,如果它不为null,则使用casted var而不是x调用foo。

这不是最面向对象的情况,但它发生了,dynamic_cast可以帮助你(好吧,一般来说,施放不是太面向对象)。