Я думаю, что есть два случая, когда использование dynamic_cast допустимо. Первый — проверить, поддерживает ли объект интерфейс, а второй — нарушить инкапсуляцию. Позвольте мне объяснить оба подробно.
Проверка интерфейса
Рассмотрим следующую функцию:
void DoStuffToObject( Object * obj )
{
ITransactionControl * transaction = dynamic_cast<ITransactionControl>( obj );
if( transaction )
transaction->Begin();
obj->DoStuff();
if( transaction )
transaction->Commit();
}
(ITransactionControl был бы чистым абстрактным классом.) В этой функции мы хотим «DoStuff» в контексте транзакции, если объект поддерживает семантику транзакции. Если это не так, все равно можно просто идти вперед.
Конечно, мы могли бы просто добавить в класс Object виртуальные методы 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 приводят к множественному выделению памяти. Хуже того, оказывается, что «итератор» почти всегда является «Итератором массива». Если бы вы только могли сделать такое предположение, то ваши проблемы с производительностью исчезли бы. С 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 не бесплатен и открыт для злоупотреблений. (И, честно говоря, я видел, что им злоупотребляют гораздо чаще, чем хорошо его используют.) Если вы обнаружите, что часто его используете, неплохо рассмотреть другие подходы.
person
Peter Ruderman
schedule
28.11.2012