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
использует и требует его существования! Таким образом, последнее приведение может использоваться только в тех классах, которые содержат хотя бы одну виртуальную функцию (например, виртуальный деструктор).
person
CygnusX1
schedule
09.06.2017