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