static_cast
может выполнять только те приведения, в которых расположение памяти между классами известно во время компиляции. dynamic_cast
может проверять информацию во время выполнения, что позволяет более точно проверять правильность приведения, а также считывать информацию во время выполнения относительно структуры памяти.
Виртуальное наследование помещает информацию времени выполнения в каждый объект, которая определяет, какова структура памяти между Base
и Derived
. Один за другим или есть дополнительный пробел? Поскольку static_cast
не может получить доступ к такой информации, компилятор будет действовать консервативно и просто выдаст ошибку компилятора.
Более подробно:
Рассмотрим сложную структуру наследования, где - из-за множественного наследования - существует несколько копий Base
. Наиболее типичным сценарием является наследование алмазов:
class Base {...};
class Left : public Base {...};
class Right : public Base {...};
class Bottom : public Left, public Right {...};
В этом сценарии Bottom
состоит из Left
и Right
, где каждый имеет свою собственную копию Base
. Структура памяти всех вышеперечисленных классов известна во время компиляции, и static_cast
может использоваться без проблем.
Давайте теперь рассмотрим подобную структуру, но с виртуальным наследованием Base
:
class Base {...};
class Left : public virtual Base {...};
class Right : public virtual Base {...};
class Bottom : public Left, public Right {...};
Использование виртуального наследования гарантирует, что при создании Bottom
оно содержит только одну копию Base
, то есть совместно используемую между частями объекта Left
и Right
, Макет объекта Bottom
может быть, например:
Base part
Left part
Right part
Bottom part
Теперь предположим, что вы разыгрываете Bottom
на Right
(это действительный состав). Вы получаете Right
указатель на объект, который состоит из двух частей: Base
и Right
имеют промежуток памяти между ними, содержащий (теперь неактуальную) часть Left
. Информация об этом промежутке хранится во время выполнения в скрытом поле Right
(обычно обозначаемом как vbase_offset
). Вы можете прочитать подробности, например, здесь .
Однако этот пробел не существовал бы, если бы вы просто создали отдельный Right
объект.
Итак, если я дам вам указатель на Right
, вы не будете знать во время компиляции, является ли он отдельным объектом или частью чего-то большего (например, Bottom
). Вам нужно проверить информацию времени выполнения, чтобы правильно привести от Right
до Base
. Вот почему static_cast
не удастся и dynamic_cast
не будет.
Примечание к dynamic_cast:
Хотя static_cast
не использует информацию об объекте во время выполнения, dynamic_cast
использует, а требует его существования! Таким образом, последнее приведение может использоваться только для тех классов, которые содержат хотя бы одну виртуальную функцию (например, виртуальный деструктор)