Я попытался создать класс, который наследуется от нескольких классов, как следует, получая "бриллиант",
(D наследуется от B и C. B и C оба наследуют от A практически):
/\
ДО НАШЕЙ ЭРЫ
\/
D
Теперь у меня есть контейнер со связанным списком, который содержит указатели на базовый класс (A).
Когда я попытался сделать явное приведение к указателю (после проверки typeid), я получил следующую ошибку:
"не может преобразовать указатель на базовый класс" A "на указатель на производный класс" D "- базовый класс является виртуальным"
Но когда я использую динамическое кастинг, кажется, все работает отлично.
Может кто-нибудь объяснить мне, почему я должен использовать динамическое кастинг и почему виртуальное наследование вызывает эту ошибку?
"Виртуальный" всегда означает "определенный во время выполнения". Виртуальная функция находится во время выполнения, а виртуальная база также находится во время выполнения. Весь смысл виртуальности заключается в том, что фактическая цель, о которой идет речь, не познается статически.
Поэтому невозможно определить наиболее производный объект, которому вы даете виртуальную базу во время компиляции, поскольку связь между базой и самым производным объектом не фиксирована. Вам нужно подождать, пока вы не узнаете, что такое фактический объект, прежде чем вы сможете определить, где он находится по отношению к базе. Это то, что делает динамический бросок.
Когда я попытался сделать явное приведение к указателю (после проверки типа)
После успешного 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_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
не будет просто включать в себя постоянное смещение как простой "статический" upcast (перед входом в конструктор объекта vptr не настроен, поэтому такое кастинг не может работать, будьте осторожны с приведением в список инициализации конструктора).
И BTW, 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
(а Lvalue неизвестного динамического типа) требует проверок времени выполнения виртуальных таблиц (или эквивалента). Возвращаясь к подобъекту D
требуется нетривиальное вычисление:
F
), используя таблицу vtable A
D
для f
, снова используя vtable Это не тривиально, так как dynamic_cast<D&>(ar)
статически ничего не знает о F
(макет F
, макет vtable F
); все будет извлечено из vtable. Все dynamic_cast
знает, что существует производный класс A
а vtable имеет всю информацию.
Конечно, в C++ нет static_exact_cast<>
, поэтому вам нужно использовать dynamic_cast
с соответствующими проверками времени выполнения; dynamic_cast
- сложная функция, но сложность охватывает случаи базового класса; когда динамический тип присваивается dynamic_cast
, избегается древовидная структура базовых классов, и тест довольно прост.
Вывод:
Либо вы назовите динамический тип в dynamic_cast<T>
и dynamic_cast
будет быстро и просто в любом случае, или вы этого не сделаете, и сложность dynamic_cast
действительно необходима.