Виртуальное множественное наследование и приведение

0

Я попытался создать класс, который наследуется от нескольких классов, как следует, получая "бриллиант",
(D наследуется от B и C. B и C оба наследуют от A практически):


/\
ДО НАШЕЙ ЭРЫ
\/
D

Теперь у меня есть контейнер со связанным списком, который содержит указатели на базовый класс (A).
Когда я попытался сделать явное приведение к указателю (после проверки typeid), я получил следующую ошибку:
"не может преобразовать указатель на базовый класс" A "на указатель на производный класс" D "- базовый класс является виртуальным"

Но когда я использую динамическое кастинг, кажется, все работает отлично.
Может кто-нибудь объяснить мне, почему я должен использовать динамическое кастинг и почему виртуальное наследование вызывает эту ошибку?

  • 6
    Подумайте об исправлении вашего дизайна. :)
  • 0
    не могли бы вы добавить код, пожалуйста?
Показать ещё 7 комментариев
Теги:
downcasting
multiple-inheritance
dynamic-cast
virtual-inheritance

2 ответа

3
Лучший ответ

"Виртуальный" всегда означает "определенный во время выполнения". Виртуальная функция находится во время выполнения, а виртуальная база также находится во время выполнения. Весь смысл виртуальности заключается в том, что фактическая цель, о которой идет речь, не познается статически.

Поэтому невозможно определить наиболее производный объект, которому вы даете виртуальную базу во время компиляции, поскольку связь между базой и самым производным объектом не фиксирована. Вам нужно подождать, пока вы не узнаете, что такое фактический объект, прежде чем вы сможете определить, где он находится по отношению к базе. Это то, что делает динамический бросок.

  • 0
    Но почему я не могу «вызвать» явное приведение после того, как проверил тип объекта? В чем разница между явным приведением после проверки типа и динамическим приведением? @Kerrek SB
  • 0
    @DavidTzoor Посмотрите здесь: stackoverflow.com/questions/15921372/…
Показать ещё 1 комментарий
0

Когда я попытался сделать явное приведение к указателю (после проверки типа)

После успешного 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 действительно необходима.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню