Возможность кастовать с базы на производную и с базы на производную - не единственная отличительная черта dynamic_cast
.Ключевой особенностью является то, что dynamic_cast
выполняет такое преобразование во время выполнения , после проверки того, что наиболее производный объект действительно соответствует (или фактически содержит ) желаемого целевого типа.,Это означает, что ошибка, если преобразование было неудачным, также является ошибкой во время выполнения, и что ваша программа должна быть подготовлена к обработке таких ошибок во время выполнения.
Какие это могут быть ошибки?dynamic_cast
может сообщить об ошибке либо путем возврата нулевого указателя, если вы выполняли приведение между типами указателей, скажем, от X*
до Y*
, либо путем выдачи исключения std::bad_cast
, если вы выполняли приведение между ссылочными типами, например X&
Y&
.
Приведение из a
со статическим типом A*
к B*
может привести к действительному указателю на B
или к нулю.Последнее именно то, что произошло в вашем примере: a
фактически указывает на законченный объект типа A
, и в нем нет подобъекта типа B
.Таким образом, приведение возвращает нулевой указатель, который затем сразу используется для вызова функции-члена down->print()
.Любая попытка разыменования значения нулевого указателя - это UB (неопределенное поведение), которое вполне может проявиться как segfault.
Защита вашей программы от такого segfault проста: просто убедитесь, что возвращаемое значение не равно null:
B* down = dynamic_cast<B*>(a);
if (down)
{
// Use down all you want
}
else
{
// Report an error, skip some actions
// or return from the function.
// But don't use `down`!
}
Что касается печати "из D", то это потому, что метод print
объявлен виртуальным.Это означает, что реализация из самого производного класса будет вызвана, даже если вы вызываете ее через указатель на базовый подобъект.