我认为有两种情况下使用dynamic_cast是一件合理的事情。第一种是检查一个对象是否支持一个接口,第二种是打破封装性。让我详细解释一下。
检查接口
考虑以下函数:
void DoStuffToObject( Object * obj )
{
ITransactionControl * transaction = dynamic_cast<ITransactionControl>( obj );
if( transaction )
transaction->Begin();
obj->DoStuff();
if( transaction )
transaction->Commit();
}
在这个函数中,如果对象支持事务语义,我们希望在事务的上下文中执行“DoStuff”。如果不支持,则可以直接进行。
现在,我们肯定可以向Object类添加virtual Begin()和Commit()方法,但是那么每一个从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导致了多次内存分配。更糟糕的是,事实证明“iterator”几乎总是一个“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不是免费的,也容易被滥用。(坦白地说,我看到它被滥用的次数比它被正确使用的次数多得多。)如果你发现自己频繁使用它,最好考虑其他方法。