Тот же самый производный класс в виртуальном наследовании = такое же смещение между родительским классом? - PullRequest
3 голосов
/ 08 мая 2019

Для определенного класса F его указатель (созданный с помощью new F()) может быть приведен к указателю базового класса, например, до B*, C*, D* и E*.

enter image description here

Гарантируется ли, что для определенного компилятора (определенной конфигурации и определенной программы .exe) разница адреса приращения (в байтах) пары любых упомянутых классов (например, выберите 2 среди B*, C*, D* и E*) каждого экземпляра new F() является константой?

Например, это MCVE печать 8 все 10 раз для меня: -

#include <iostream>
class B{ public: virtual ~B(){} };
class D: virtual public B{};
class C: virtual public B{};
class E: virtual public D{};
class F: virtual public E, virtual public C{};
int main(){
    for(int n=0;n<10;n++){
        F* f = new F();
        C* c=f;
        E* e=f;
        int offset=
            (reinterpret_cast<uintptr_t>(c))
            -
            (reinterpret_cast<uintptr_t>(e));
        std::cout<<offset<<std::endl;
    }
}

Я верю, что ответ - да, потому что я могу static_cast.
(и я позорно полагаюсь на это предположение бессознательно в течение длительного времени.)

Разный компилятор может печатать разные смещения, но меня волнует только случай определенной (одинаковой) программы и определенного (одинакового) истинного базового класса (new F()).

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

Я был бы рад, если бы в ответе была также указана некоторая спецификация C ++.

Редактировать: Исправить несоответствие в моем коде (Спасибо curiousguy комментарий)

1 Ответ

1 голос
/ 09 мая 2019

[Предисловие: Терминология:

Для простоты мы примерно следуем терминологии Itanium C ++ ABI и обобщаем / обобщаем:

Надлежащая база B of D - это настоящий базовый класс, отличный от D.Неподходящая база - это правильная база или D сама.

Правильный подобъект класса D - это член или правильная база;неподходящий субъект - это правильный подобъект или D.

- предисловие конца]

[Предисловие: Ошибочное предположение:

Вы, похоже, находитесь под впечатлением, чтовыражение преобразования типа как static_cast каким-то образом гарантирует, что сложный код не будет сгенерирован.Это не тот случай, static_cast может вызывать все, что может сделать прямая инициализация, включая вызов конструктора: static_cast<String>("")

- конец предисловия]

Нет вероятных причин дляреализация, которая не помещает каждый подобъект базового класса с фиксированным известным смещением в законченном (или наиболее производном, имеющем ту же структуру на практике) объекте типа D;поэтому возникает вопрос: неужели ничто не мешает извращенной реализации сделать это?

Что бы потребовалось для реализации, чтобы иметь различные макеты, и будет ли такая реализацияв соответствии?Нам нужно перечислить движения указателя (неявные преобразования или приведения), поддерживаемые внутри иерархии классов.

[Обратите внимание, что доступ к унаследованному (не статическому) элементу данных определяется (неявным) преобразованием this, за которым следует доступ к не унаследованному члену данных, и поэтому это вызов унаследованного не статическогофункция.]

Для:

  • X (неправильный) базовый подобъект D
  • Y (правильный) базовый подобъект X (на самом деле не имеет значения, что Y на самом деле является правильным)
  • Z - это еще одна (правильная) основа D

ASCII-art резюме (дерево может бытьсвернуто):

Y
|
X     Z
 \   /
   D

Эти базы должны быть однозначными: базовый подобъект Y должен быть единственным в этом типе в X.(Но косвенная база Y из D не обязательно должна быть однозначной: D::Y не может обозначать единственную базу, только D::X::Y должна быть однозначной.)

Три вида "простых"Должны поддерживаться движения иерархии:

  • (вверх) преобразование (которое может быть сделано неявно) из X* в Y*
  • (вниз NV) для не виртуальной базы Yиз X: приведение вниз от Y* до X* может быть выполнено с помощью static_cast
  • (вниз P) для (возможно, виртуальной) полиморфной основы Y из X: приведение вниз от Y* до X* может быть выполнен с помощью dynamic_cast

Другое более сложное движение - dynamic_cast для Y* до Z*;это два движения: бросок вниз, затем бросок вверх, проход через самый производный объект D, который не обязательно должен быть статически известного типа.(Код, выполняющий эти два шага в явном виде, должен быть в состоянии назвать D.)

Обычно эти операции выполняются, по крайней мере, для частично построенных объектов.

(Стандарт C ++ не имеетпринял четкое решение о том, поддерживается ли преобразование в указатель на не виртуальную базу для указателя на не построенный объект. Ничто, связанное с виртуальной базой, явно не может быть выполнено для не построенного объекта.)

Таким образом, на практике (неподходящий) подобъект X должен содержать достаточно информации, чтобы найти свои виртуальные базы, обычно либо путем явного помещения их адресов в скрытые члены, либо путем сохранения их смещений в виртуальной таблице.

[Это подразумевает, что при создании правильного базового класса с виртуальными базами виртуальная таблица не может быть такой же, как виртуальная таблица для полного объекта.Это не похоже на конструкцию базовых подобъектов (виртуальных или нет) только с не виртуальными базами.]

Если виртуальные базы расположены через vtable, это означает, что больше нет возможных компоновок классов, чем отдельные vtables.Конструктор самого производного класса не сможет рандомизировать макет (если только vtables не генерируется на месте для описания указанного макета).

Если виртуальные базы расположены через скрытые элементы данных, может показаться, чточто существует большая гибкость для порочной реализации.Это усугубляется тем фактом, что должны поддерживаться приведения вниз из полиморфной виртуальной базы: полиморфная база знает только свой динамический тип (через vptr во всех существующих реализациях).Производный класс может хранить массив адресов (или смещений) (некоторые, любые) своих базовых классов, , но база не может хранить информацию о каждом из своих производных классов , по построению, так как его расположение должно бытьперед определением, какие классы будут из него получены (легко видеть, что sizeof(T) не может быть даже в самой извращенной реализации монотонной функцией определений классов, не используемых в T и использующих T).

Но извращенная реализация может по-прежнему поддерживать несколько макетов, используя один из следующих подходов:

(множественные таблицы)

Если виртуальные таблицы генерируются наspot, или если много vtables создано a priori , чтобы учесть различные макеты класса, имеющего виртуальные базы, то полиморфная база может иметь доступ к достаточному количеству информации, чтобы выполнить любую понижающую оценку.

[Обратите внимание, что между уникальными типами и виртуальными таблицами в целом уже нет сопоставления один к одному, поэтому проверка на равенство typeid, дажевыражения одного и того же типа не могут быть сравнением адресов между vptr в целом.В общем, равенство типов реализуется путем сравнения указателей typeinfo.]

[Обратите внимание, что если вы используете «DLL» и динамический компоновщик, различные «эквивалентные» (идентичные по своему символическому определению перед связыванием) таблицы информации о типах(структуры vtables и typeinfo) могут существовать по разным адресам, но эти не объединяющие ссылки в любом случае нарушают ODR, поскольку статические объекты также не будут объединены.]

(BLIP)

Каждый полиморфный потенциальный базовый класс (который, возможно, может использоваться в качестве базового класса, так что конечные или локальные классы могут быть освобождены) будет иметь по крайней мере один дополнительный скрытый член: указатель (альтернативно относительныйсмещение) к члену в наиболее производном классе: (базовая таблица) , скрытый нестатический элемент таблицы.

Список (базовая таблица) будет отображать (некоторые, любые)адреса базового класса (или смещения), локаторы баз (те базы, которые порочные реализации хотят изменить).

BLIP (base-locators-info-ptr): указатель на структуру типа typeinfo, которая содержит описание макета базовых локаторов, поскольку макет зависит от типа самого производного класса, который не известен во время компиляции;обратите внимание, что BLIP может храниться внутри виртуальной таблицы как зависимый от типа, а не зависимый от экземпляра.

Преобразования вниз обнаружат (базовую таблицу), которая непрозрачна и ее невозможно интерпретировать с помощью кода, который знает только обазовый класс, а затем использовать BLIP для его декодирования, например, данные типа info содержат код для навигации в базовых классах для реализации динамического понижения или повышения dynamic_cast.

Это может показаться исключительно сложным и трудным для получения правильных результатов.и с какой практической целью?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...