Прежде чем вдаваться в детали dynamic_cast, стоит отметить, что большинство реализаций этого оператора работают довольно медленно. Так, по крайней мере, одна из распространенных реализаций основана на сравнении имен классов, представленных строками. Если вы выполняете dynamic_cast для объекта класса, принадлежащего иерархии с одиночным наследованием глубиной в четыре уровня, то каждое обращение к dynamic_cast в такой реализации может обойтись вам в четыре вызова strcmp для сравнения имен классов. Для более глубокой иерархии или такой, в которой имеется множественное наследование, эта операция окажется еще более дорогостоящей. Есть причины, из-за которых некоторые реализации работают подобным образом (потому что они должны поддерживать динамическую компоновку). Таким образом, в дополнение к настороженности по отношению к приведениям типов в принципе вы должны проявлять особый скептицизм, когда речь идет о применении dynamic_cast в части программы, для которой производительность стоит на первом месте.
Необходимость в dynamic_cast обычно появляется из-за того, что вы хотите выполнить операции, определенные в производном классе, для объекта, который, как вы полагаете, принадлежит производному классу, но при этом у вас есть только указатель или ссылка на базовый класс, посредством которой нужно манипулировать объектом. Есть два основных способа избежать этой проблемы.
Первый – используйте контейнеры для хранения указателей (часто «интеллектуальных», см. правило 13) на сами объекты производных классов, тогда отпадет необходимость манипулировать этими объектами через интерфейсы базового класса. Например, если в нашей иерархии Window/SpecialWindow только SpecialWindow поддерживает мерцание (blinking), то вместо:
class Window { ...};
class SpecialWindow {
public:
void blink();
...
};
typedef // см. правило 13
std::vector
VPW winPtrs;
...
for (VPW::iterator iter = winPtrs.begin(); // нежелательный код:
iter!=winPtrs.end(); // применяется dynamic_cast
++iter){
if(SpecialWindow psw = dynamic_cast
psw->blink();
}
попробуйте сделать так:
typedef std::vector
VPSW winPtrs;
...
for (VPSW::iterator iter = winPtrs.begin(); // это лучше:
iter != winPtrs.end(); // не использует dynamic_cast
++iter)
(*iter)->blink();
Конечно, такой подход не позволит вам хранить указатели на объекты всех возможных производных от Window классов в одном и том же контейнере. Чтобы работать с разными типами окон и обеспечить безопасность по отношению к типам, вам может понадобиться несколько контейнеров.
Альтернатива, которая позволит манипулировать объектами всех возможных производных от Window классов через интерфейс базового класса, – это предусмотреть виртуальные функции в базовом классе, которые позволят вам делать именно то, что вам нужно. Например, хотя только SpecialWindow умеет мерцать, может быть, имеет смысл объявить функцию в базовом классе и обеспечить там реализацию по умолчанию, которая не делает ничего:
class Window {
public:
virtual void blink() {} // реализация по умолчанию – пустая
... // операция, см. в правиле 34 – почему
}; // наличие реализации по умолчанию
// может оказаться неудачной идеей
class SpecialWindow: public Window {
public:
virtual void blink() {...}
...
};
typedef std::vector
VPW winPtrs; // контейнер содержит
// (указатели на) все возможные
... // типы окон
for(VPW::iterator iter = winPtrs.begin();
iter != winPtrs.end();
++iter) // dynamic_cast не используется
(*iter)->blink();
Ни один из этих подходов – с применением безопасных по отношению к типам контейнеров или перемещением виртуальной функции вверх по иерархии – не является универсально применимым, но во многих случаях они представляют полезную альтернативу dynamic_cast. Пользуйтесь ими, когда возможно.
Но вот чего стоит избегать всегда – это каскадов из операторов dynamic_cast, то есть чего-то вроде такого кода:
class Window {...};
... // здесь определены производные классы
typedef std::vector