Я думаю, что есть два случая, когда использование 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;
}
}
Еще раз, мы могли бы добавить виртуальный метод «CopyElements» в класс IIterator, но это имеет те же недостатки, которые я упомянул выше. А именно, это раздувает интерфейс. Это заставляет всех разработчиков иметь метод CopyElements, хотя ArrayIterator является единственным классом, который сделает что-то интересное в нем.
Несмотря на это, я рекомендую использовать эти методы экономно. dynamic_cast не является бесплатным и открыт для злоупотреблений. (И, честно говоря, я видел, что он злоупотреблял гораздо чаще, чем видел, как он использовался хорошо.) Если вы обнаружите, что часто его используете, неплохо рассмотреть другие подходы.