Я попытался создать класс, который наследует от нескольких классов, как показано ниже, получив «алмаз»
(D наследует от B и C. B и C оба наследуют от A фактически):
/ \
ДО НАШЕЙ ЭРЫ
\ /
D
Теперь у меня есть контейнер со связанным списком, который содержит указатели на базовый класс ().
Когда я попытался сделать явное приведение к указателю (после проверки typeid), я получил следующую ошибку:
«невозможно преобразовать указатель на базовый класс« A »в указатель на производный класс« D »- базовый класс является виртуальным»
Но когда я использую динамическое приведение, это, кажется, работает просто отлично.
Может кто-нибудь объяснить мне, почему я должен использовать динамическое приведение и почему виртуальное наследование вызывает эту ошибку?
«Виртуальный» всегда означает «определенный во время выполнения». Виртуальная функция находится во время выполнения, а виртуальная база также находится во время выполнения. Весь смысл виртуальности заключается в том, что фактическая цель, о которой идет речь, статически недоступна для понимания.
Следовательно, невозможно определить наиболее производный объект, для которого вам предоставляется виртуальная база во время компиляции, поскольку связь между базой и наиболее производным объектом не является фиксированной. Вы должны ждать, пока вы не знаете, какие фактический объект находится прежде, чем вы сможете решить, где он находится по отношению к основанию. Это то, что делает динамический актерский состав.
Когда я попытался сделать явное приведение к указателю (после проверки typeid)
После успешного typeid(x) == typeid(T)
Вы знаете динамический тип x
и вы можете теоретически избежать любой другой проверки времени выполнения, связанной с dynamic_cast
в таком случае. OTOH, компилятор не обязан делать этот вид статического анализа.
static_cast<T&>(x)
не передает компилятору знания о том, что динамический тип x
на самом деле T
: предварительное условие слабее (что T
объект имеет x
как подобъект базового класса).
C ++ может обеспечить static_exact_cast<T&>(x)
который действителен только если x
обозначает объект динамического типа T
(а не какой-то тип, полученный из T
, В отличие от static_cast<T&>
). Это гипотетически static_exact_cast<T&>(x)
, предполагая динамический тип x
является T
, пропустит любую проверку во время выполнения и вычислит правильный адрес из знания T
макет объекта: потому что в
D d;
B &br = d;
вычисление смещения во время выполнения не требуется, в static_exact_cast<D&>(br)
обратная настройка не будет включать вычисления смещения во время выполнения.
Дано
B &D_to_B (D &dr) {
return dr;
}
вычисление смещения во время выполнения необходимо в D_to_B
(кроме случаев, когда весь анализ программы показывает, что ни один класс не получен из D
имеет различное смещение базового класса A
); дано
struct E1 : virtual A
struct E2 : virtual A
struct F : E1, D, E2
макет D
подобъект F
будет отличаться от макета D
завершенный объект: A
подобъект будет с другим смещением. Смещение, необходимое для D_to_B
будет дано vtable из D
(или хранится непосредственно в объекте); это означает, что D_to_B
не будет просто включать постоянное смещение в качестве простого «статического» преобразования (перед входом в конструктор объекта vptr не настроен, поэтому такое приведение не может работать; будьте осторожны с приведением вверх в списке инициаторов конструктора).
И кстати, D_to_B (d)
не отличается от static_cast<B&> (d)
, так что вы видите, что вычисление смещения во время выполнения может быть сделано внутри static_cast
,
Рассмотрим следующий код, который компилируют наивно ar
имеет динамический тип F
):
F f;
D &dr = f; // static offset
A &ar = dr; // runtime offset
D &dr2 = dynamic_cast<D&>(ar);
Нахождение A
базовый класс предмет из ссылки на D
(значение неизвестного динамического типа) требует проверки во время выполнения виртуальной таблицы (или ее эквивалента). Возвращаясь к D
подобъект требует нетривиального вычисления:
F
), используя vtable of A
D
подобъект базового класса f
снова используя vtableЭто не тривиально, как dynamic_cast<D&>(ar)
статически не знает ничего конкретного F
(макет F
Макет виртуальной таблицы F
); все взято из vtable. Все dynamic_cast
знает, что есть производный класс A
и у vtable есть вся информация.
Конечно, нет static_exact_cast<>
в C ++, поэтому вы должны использовать dynamic_cast
с соответствующими проверками во время выполнения; dynamic_cast
является сложной функцией, но сложность охватывает случаи базового класса; когда динамический тип задан dynamic_cast
обход дерева базовых классов исключается, и тест довольно прост.
Заключение:
Либо вы называете динамический тип в dynamic_cast<T>
а также dynamic_cast
это быстро и просто в любом случае, или вы не делаете, и сложность dynamic_cast
действительно нужно.