19

我被多次告知(并且在实践中看到自己)使用 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()BaseDerived.

在那种情况下,我的问题是,为什么我们在语言中有 dynamic_cast 呢?是否有使用 dynamic_cast 合理的示例?

4

5 回答 5

30

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

本质上,虚函数只在某些情况下有效,而不是全部

于 2011-04-25T09:06:13.717 回答
20

我认为在两种情况下使用 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 不是免费的,而且很容易被滥用。(坦率地说,我看到它被滥用的次数比我看到它使用得好的次数要多得多。)如果你发现自己经常使用它,那么考虑其他方法是个好主意。

于 2012-11-28T17:09:34.980 回答
7

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

是的。这就是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通常表示您无法使用通用接口(即函数)来实现您的目标,因此您需要将其强制转换为精确类型,以便调用类型基类/派生类的特定成员函数。

于 2011-04-25T08:58:33.147 回答
0

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

于 2011-04-25T08:57:42.150 回答
0

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

BaseClass* x = new DerivedClass();

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

一种解决方案是使用 dynamic_cast 并针对 NULL 对其进行测试,如果它不为 null,则使用强制转换的 var 而不是 x 调用 foo。

这不是最面向对象的情况,但它确实发生了,而 dynamic_cast 可以帮助您解决它(好吧,一般来说,强制转换不太面向对象)。

于 2011-04-25T09:24:56.770 回答