简体   繁体   English

dynamic_cast的正确用例是什么?

[英]What is the proper use case for dynamic_cast?

I have been told many times (and seen myself in practice) that the use of dynamic_cast often means bad design, because it can and should be replaced with virtual functions. 我多次被告知(并且在实践中看到自己)使用dynamic_cast通常意味着糟糕的设计,因为它可以而且应该被虚函数替换。

For example, consider the following code: 例如,请考虑以下代码:

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
}

It can be easily seen that instead of writing dynamic casts we can just add a virtual function doStuff() to Base and re-implement it in Derived . 可以很容易地看出,我们只需要向Base添加一个虚函数doStuff() ,然后在Derived重新实现它,而不是编写动态强制转换。

In that case, my question is, why do we have dynamic_cast in the language at all? 在这种情况下,我的问题是,为什么我们在语言中都有dynamic_cast? Is there an example in which the use of dynamic_cast is justified? 有没有一个例子,使用dynamic_cast是合理的?

The trouble with virtual functions is that all classes in the hierarchy must have an implementation or be abstract, and that's definitely not always the right thing to do. 虚函数的问题在于层次结构中的所有 必须具有实现或是抽象的,并且这绝对不是正确的事情。 For example, what if Base is an interface, and in the if, you need to access the internal implementation details of Derived ? 例如,如果Base是一个接口,那么在if中,您需要访问Derived的内部实现细节吗? That's certainly not doable in a virtual function. 这在虚拟功能中肯定是行不通的。 In addition, dynamic_cast is needed for both upcasting and downcasting in certain multiple inheritance situations. 此外,在某些多重继承情况下,需要使用dynamic_cast进行向上转换和向下转换。 And there are limits as to what can be done in virtual functions- for example, templates. 在虚拟功能中可以做些什么是有限制的 - 例如,模板。 And finally, sometimes you need to store a Derived* , not just call a function on it. 最后,有时您需要存储Derived* ,而不仅仅是调用函数。

Essentially, virtual functions only work in some cases, not all of them. 实质上,虚函数仅在某些情况下有效,而不是全部

I think there are two cases where using dynamic_cast is a valid thing to do. 我认为有两种情况使用dynamic_cast是有效的事情。 The first is to check if an object supports an interface, and the second is to break encapsulation. 第一个是检查对象是否支持接口,第二个是打破封装。 Let me explain both in detail. 让我详细解释一下。

Checking for an Interface 检查接口

Consider the following function: 考虑以下功能:

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

    if( transaction )
        transaction->Begin();

    obj->DoStuff();

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

(ITransactionControl would be a pure abstract class.) In this function, we want to "DoStuff" in the context of a transaction if the object supports transaction semantics. (ITransactionControl将是一个纯抽象类。)在此函数中,如果对象支持事务语义,我们希望在事务上下文中使用“DoStuff”。 If it doesn't, it's fine to just go ahead anyway. 如果没有,那么无论如何都可以继续。

Now we certainly could just add virtual Begin() and Commit() methods to the Object class, but then every class that derives from Object gets Begin() and Commit() methods, even if they have no awareness of transactions. 现在我们当然可以向Object类添加虚拟的Begin()和Commit()方法,但是随后派生自Object的每个类都会获得Begin()和Commit()方法,即使它们没有事务意识。 Use of virtual methods in the base class simply pollutes its interface in this case. 在这种情况下,在基类中使用虚拟方法只会污染其接口。 The example above promotes better adherance to both the single responsibility principle and the interface segregation principle. 上面的例子促进了对单一责任原则和界面隔离原则的更好的遵守。

Breaking Encapsulation 打破封装

This may seem like strange advice considering that dynamic_cast is generally considered harmful because it allows you to break encapsulation. 考虑到dynamic_cast通常被认为是有害的, 因此它可能看起来很奇怪, 因为它允许您打破封装。 However, done correctly, this can be a perfectly safe and powerful technique. 但是,如果操作正确,这可能是一种非常安全和强大的技术。 Consider the following function: 考虑以下功能:

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

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

   return result;
}

There's nothing wrong here. 这里没有错。 But now suppose that you start seeing performance problems in the field. 但现在假设您开始在现场看到性能问题。 After analyzing, you find that your program is spending an auwful lot of time inside this function. 经过分析,您会发现您的程序在此功能中花费了大量时间。 The push_backs result in multiple memory allocations. push_backs导致多个内存分配。 Even worse, it turns out that "iterator" is almost always an "ArrayIterator". 更糟糕的是,事实证明“迭代器”几乎总是“ArrayIterator”。 If only you were able to make that assumption, then your performance problems would disappear. 如果只有你能够做出这样的假设,那么你的表现问题就会消失。 With dynamic_cast, you can do exactly that: 使用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;
   }
}

Once again, we could add a virtual "CopyElements" method to the IIterator class, but this has the same drawbacks I mentioned above. 再一次,我们可以向IIterator类添加一个虚拟的“CopyElements”方法,但这与我上面提到的相同。 Namely, it bloats the interface. 也就是说,它使界面膨胀。 It forces all implementors to have a CopyElements method, even though ArrayIterator is the only class that will do something interesting in it. 它强制所有实现者都有一个CopyElements方法,即使ArrayIterator是唯一会在其中做一些有趣事情的类。

All that being said, I recommend using these techniques sparingly. 话虽如此,我建议谨慎使用这些技术。 dynamic_cast is not free and is open to abuse. dynamic_cast不是免费的,可以滥用。 (And frankly, I've seen it abused far more often than I've seen it used well.) If you find yourself using it a lot, it's a good idea to consider other approaches. (坦率地说,我看到它的使用频率远远超过我看到它使用得很好。)如果你发现自己经常使用它,那么考虑其他方法是个好主意。

It can be easily seen that instead of writing dynamic casts we can just add a virtual function doStuff() to Base and re-implement it in Derived. 可以很容易地看出,我们只需要向Base添加一个虚函数doStuff(),然后在Derived中重新实现它,而不是编写动态强制转换。

YES. 是。 That is what virtual functions are for. 这就是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;

Did you notice how virtual function eliminates dynamic_cast ? 您是否注意到virtual功能如何消除dynamic_cast

Use of dynamic_cast usually indicates that you cannot acheive your goal using common interface (ie virtual functions), hence you need to cast it to exact type, so as to call the specific member functions of type base/derived classes. 使用dynamic_cast通常表示您无法使用通用接口(即函数)实现目标,因此您需要将其强制转换为精确类型,以便调用类型基类/派生类的特定成员函数。

The subclass may have other methods not present in the base class, and that may not make sense in the context of the other subclasses. 子类可能具有基类中不存在的其他方法,并且在其他子类的上下文中可能没有意义。 But generally you should avoid it. 但一般来说你应该避免它。

What if, you have a method (call it foo) which receives BaseClass*, and it's expended for DerivedClass*. 如果你有一个接收BaseClass *的方法(称之为foo),并且它已经用于DerivedClass *,那该怎么办? If I'll write: 如果我写的话:

BaseClass* x = new DerivedClass();

and call foo with x, I'll get to foo (BaseClass varName), and not foo (DerivedClass varName). 并用x调用foo,我将得到foo(BaseClass varName),而不是foo(DerivedClass varName)。

One solution is to use dynamic_cast and test it for against NULL, and if it's not null, call foo with the casted var and not x. 一种解决方案是使用dynamic_cast并针对NULL测试它,如果它不为null,则使用casted var而不是x调用foo。

It's not the most object oriented situation, but it happens, and dynamic_cast can help you with it (well, casting in general is not too object oriented). 它不是最面向对象的情况,但它发生了,而dynamic_cast可以帮助你(好吧,一般来说,投射不是太面向对象)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM