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
}
很容易看出,我们不需要编写动态转换,只需向 Base
添加一个虚函数 doStuff()
并重新实现它即可在派生
中。
在这种情况下,我的问题是,为什么我们的语言中有dynamic_cast?有没有一个例子可以证明使用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.
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
.
In that case, my question is, why do we have dynamic_cast in the language at all? Is there an example in which the use of dynamic_cast is justified?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
虚函数的问题在于层次结构中的所有类必须有一个实现或者是抽象的,而这绝对不总是正确的做法。例如,如果
Base
是一个接口,而在if中,您需要访问Derived
的内部实现细节,该怎么办?这在虚函数中当然是不可能的。此外,在某些多重继承情况下,向上转型和向下转型都需要dynamic_cast
。在虚拟函数中可以做的事情也有限制——例如模板。最后,有时您需要存储Derived*
,而不仅仅是调用其上的函数。本质上,虚拟函数仅在某些情况下起作用,而不是在全部情况下起作用。
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 ofDerived
? 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. And there are limits as to what can be done in virtual functions- for example, templates. And finally, sometimes you need to store aDerived*
, not just call a function on it.Essentially, virtual functions only work in some cases, not all of them.
我认为在两种情况下使用dynamic_cast是有效的。第一个是检查对象是否支持接口,第二个是打破封装。让我详细解释一下两者。
检查接口
考虑以下函数:
(ITransactionControl 将是一个纯抽象类。)在此函数中,如果对象支持事务语义,我们希望在事务上下文中“DoStuff”。如果没有,那么无论如何都可以继续。
现在我们当然可以将虚拟的 Begin() 和 Commit() 方法添加到 Object 类,但是每个从 Object 派生的类都会获得 Begin() 和 Commit() 方法,即使它们没有交易意识。在这种情况下,在基类中使用虚拟方法只会污染其接口。上面的示例促进了更好地遵守单一责任原则和接口隔离原则。
破坏封装
考虑到dynamic_cast通常被认为是有害的,因为它允许您破坏封装,这似乎是一个奇怪的建议。然而,如果操作正确,这可能是一种非常安全且强大的技术。考虑以下函数:
这里没有任何问题。但现在假设您开始在现场看到性能问题。经过分析,你发现你的程序在这个函数中花费了大量的时间。 Push_backs 导致多次内存分配。更糟糕的是,事实证明“迭代器”几乎总是“ArrayIterator”。如果您能够做出这样的假设,那么您的性能问题就会消失。使用dynamic_cast,您可以做到这一点:
我们可以再次向 IIterator 类添加一个虚拟的“CopyElements”方法,但这具有我上面提到的相同缺点。也就是说,它使界面变得臃肿。它强制所有实现者都拥有 CopyElements 方法,即使 ArrayIterator 是唯一可以在其中执行一些有趣操作的类。
话虽如此,我建议谨慎使用这些技术。 Dynamic_cast 不是免费的并且容易被滥用。 (坦率地说,我看到它被滥用的次数远远多于我看到它被良好使用的次数。)如果您发现自己经常使用它,那么考虑其他方法是个好主意。
I think there are two cases where using dynamic_cast is a valid thing to do. 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:
(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. 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. 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. However, done correctly, this can be a perfectly safe and powerful technique. Consider the following function:
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. Even worse, it turns out that "iterator" is almost always an "ArrayIterator". If only you were able to make that assumption, then your performance problems would disappear. With dynamic_cast, you can do exactly that:
Once again, we could add a virtual "CopyElements" method to the IIterator class, but this has the same drawbacks I mentioned above. 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.
All that being said, I recommend using these techniques sparingly. dynamic_cast is not free and is open to abuse. (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.
是的。这就是虚拟函数的用途。
您是否注意到
virtual
函数如何消除dynamic_cast
?使用
dynamic_cast
通常表明你无法使用通用接口(即虚拟函数)来实现你的目标,因此你需要将其转换为精确的类型,以便调用特定的类型。类型基/派生类的成员函数。YES. That is what
virtual
functions are for.Did you notice how
virtual
function eliminatesdynamic_cast
?Use of
dynamic_cast
usually indicates that you cannot acheive your goal using common interface (i.e virtual functions), hence you need to cast it to exact type, so as to call the specific member functions of type base/derived classes.子类可能具有基类中不存在的其他方法,并且这在其他子类的上下文中可能没有意义。但一般来说你应该避免它。
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.
如果您有一个接收 BaseClass* 的方法(称为 foo),并且将其用于 DerivedClass*,该怎么办?
如果我写:
并用 x 调用 foo,我将得到 foo (BaseClass varName),而不是 foo (DerivedClass varName)。
一种解决方案是使用dynamic_cast并测试它是否为空,如果它不为空,则使用转换后的var而不是x调用foo。
这不是最面向对象的情况,但它确实发生了,而dynamic_cast可以帮助你解决这个问题(嗯,一般来说,转换不太面向对象)。
What if, you have a method (call it foo) which receives BaseClass*, and it's expended for DerivedClass*.
If I'll write:
and call foo with x, I'll get to foo (BaseClass varName), and not 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.
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).