我多次被告知(并且在实践中看到自己)使用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是合理的?
答案 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可以帮助你(好吧,一般来说,施放不是太面向对象)。