#c++ #классы #указатели #rtti
class A;
class B : public A;
B* b;
A* a = dynamic_cast(b); // 1
A* a = (A*)b; // 2
Для чего нужен dynamic_cast? Какие преимущества имеет запись 1 по сравнению с записью
2 и какие недостатки? В каких случаях какой вариант следует использовать?
Ответы
Ответ 1
Вообще-то, в том варианте, как вы записали, не нужно приводить ничего, потому что
объект производного класса уже ЯВЛЯЕТСЯ объектом базового. Так что можно просто писать
A * a = b;
А вот если наоборот -
B * b = a;
начинаются проблемы. Потому что обычно это говорит о плохом проектировании. Вы хотите
использовать именно потомка там, где используется только предок. Т.е. по сути дописать
какое-то хитрое поведение там, где о нем ничего не известно и не должно быть известно.
dynamic_cast позволяет не обрушить всю программу потому только, что вы сделали что-то
неверно. Ну, например,
void func(Base*b)
{
((Derived*)b)->derivedfunc();
}
Здесь вы вызываете функцию-член, которая есть у Derived, но которой нет в Base. Но
что будет, если вы действительно передадите в функцию указтель на Base? Указатель на
другого потомка Base, который ничего об этой функции не знает?
А dynamic_cast позволяет по крайней мере убедиться, что происходит именно то, что
нужно, т.е. что там действительно указатель на Derived.
Что до приведения в стиле C - (A*)b - то тут, пожалуй, ближе всего reinterpret_cast
- просто рассматривать биты как имеющие иной тип. Без каких-либо проверок. Что уже
опасно, но, по крайней мере, в тексте программы в глаза будет бросаться это длинное
слово - reinterpret_cast - как указатель на опасность. И это не шутка, это я повторяю
Страуструпа - насчет длинных неуклюжих названий ..._cast.
P.S. Мне нет нужды напоминать, что при использовании dynamic_cast требуется наличие
виртуальных функций, и пояснять, почему?
Ответ 2
Нет ни одной причины использовать запись типа 2. Она появилась как обеспечение совместимости
с приведением типов из С и старых версий С++.
По нынешним нормам НУЖНО использовать dynamic_cast.
Если Вы хотите понять чем отличаются разные варианты cast'ов - дайте знать. Но в
данном случае все тривиально. Мы приводим объект класса-потомка к базовому классу
Дополнительно обращу внимание, что cast'ы хороши, что они в принципе укладываются
в систему обработки исключений и дают человеческие коды ошибок. А использование c-style
приведения - это самый простой ( и не лучший!!! ) способ заткнуть компилятор. И замаскировать
ошибку, а потом героически пытаться ее найти и/или исправить
Ответ 3
dyanmic_cast нужен для того чтобы безопасно выполнить понижающее приведение(от типа
базового класса к типу наследника). Например
calss Base{
public:
virtual ~Base(){}
};
calss A : public Base{};
calss B : public Base{};
void foo(A *a){
Base *base = a;
//1 Нормально скомпилируется.
//Приведение типа выполнится, но на этапе выполнения
//получится какая-то чепуха, причину которой будет
//найти ой как непросто
B b1* = (B*)base;
//2 Полностью идентично 1.
//Просто такую запись проще найти при помощи grep
//или похожих средств. Так же static_cast не
//убирает модификаторы const и volatile, что в
//некоторых случая позволит избежать ошибок
B b2* = static_cast(base);
//3 Также как и случаях 1 и 2 успешно скомпилируется.
//Но b3 будет равен 0. dynamic_cast используя RTTI
//определил что произошла попытка приведения к
//неправильному типу, и вернул 0.
B b3* = dynamic_cast(base);
//Теперь мы можем написать
if(b3 == 0){
std::cerr << "что-то пошло не так\n";
}
//И быстро локализовать ошибку
}
Так же для того чтобы можно было использовать dynamic_cast в базовом классе должна
быть хотя бы одна виртуальная функция(деструктор вполне подойдет).
Если использовать вместо указателей ссылки, то dyamic_cast будет выбрасывать исключение
std::bad_cast, если происходит неправильное приведение