Виртуальные таблицы и виртуальные указатели для множественного виртуального наследования и приведения типов.

StackOverflow https://stackoverflow.com/questions/3324721

Вопрос

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

  1. Учитывать B наследует от A и оба определяют виртуальные функции f().Из того, что я узнал, представление объекта класса B в памяти выглядит так:[ vptr | A | B ]и vtbl что vptr указывает на содержит B::f().Я также понял, что приведение объекта из B к A ничего не делает, кроме игнорирования B часть в конце объекта.Это правда?Разве такое поведение не является неправильным?Нам нужен этот объект типа A выполнить A::f() метод и не B::f().

  2. Есть ли ряд vtables в системе как количество классов?

  3. Как будет vtable как выглядит класс, который наследуется от двух или более классов?Как объект C будет представлен в памяти?

  4. То же, что и вопрос 3, но с виртуальным наследованием.

Это было полезно?

Решение

Следующее верно для GCC (и похоже верно для LLVM связь), но это также может быть справедливо для используемого вами компилятора.Все это зависит от реализации и не регулируется стандартом C++.Однако GCC пишет свой собственный двоичный стандартный документ, Итаниум ABI.

В рамках моего руководства я попытался объяснить основные понятия о том, как располагаются виртуальные таблицы, более простыми словами. статья о производительности виртуальных функций в C++, которые могут оказаться вам полезными.Вот ответы на ваши вопросы:

  1. Более правильный способ изображения внутреннего представления объекта:

    | vptr | ======= | ======= |  <-- your object
           |----A----|         |
           |---------B---------|
    

    B содержит его базовый класс A, он просто добавляет пару своих участников после его окончания.

    Кастинг из B* к A* действительно ничего не делает, он возвращает тот же указатель, и vptr остается такой же.Но, вкратце, виртуальные функции не всегда вызываются через vtable.Иногда они вызываются так же, как и другие функции.

    Вот более подробное объяснение.Следует различать два способа вызова функции-члена:

    A a, *aptr;
    a.func();         // the call to A::func() is precompiled!
    aptr->A::func();  // ditto
    aptr->func();     // calls virtual function through vtable.
                      // It may be a call to A::func() or B::func().
    

    Дело в том, что это известно во время компиляции как будет вызываться функция:через vtable или просто будет обычный вызов.И дело в том, что тип выражения приведения известен во время компиляции, и поэтому компилятор выбирает правильную функцию во время компиляции.

    B b, *bptr;          
    static_cast<A>(b)::func(); //calls A::func, because the type
       // of static_cast<A>(b) is A!
    

    В этом случае он даже не заглядывает внутрь vtable!

  2. В общем, нет.Класс может иметь несколько виртуальных таблиц, если он наследуется от нескольких баз, каждая из которых имеет свою собственную виртуальную таблицу.Такой набор виртуальных таблиц образует «группу виртуальных таблиц» (см.3).

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

  3. Вот пример.Предполагать C наследует от A и B, каждый класс определяет virtual void func(), а также a,b или c виртуальная функция, соответствующая ее названию.

    А C будет иметь группу виртуальных таблиц из двух виртуальных таблиц.Он будет использовать одну виртуальную таблицу с A (виртуальная таблица, в которую помещаются собственные функции текущего класса, называется «основной»), а виртуальная таблица для B будет добавлено:

    | C::func()   |   a()  |  c()  || C::func()  |   b()   |
    |---- vtable for A ----|        |---- vtable for B ----| 
    |--- "primary virtual table" --||- "secondary vtable" -|
    |-------------- virtual table group for C -------------|
    

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

  4. Виртуальные базы (некоторые из них) выложены в конце группы vtable.Это сделано потому, что у каждого класса должна быть только одна виртуальная база, и если они были смешаны с «обычными» виртуальными таблицами, то компилятор не сможет повторно использовать части построенных виртуальных таблиц для создания производных классов.Это приведет к вычислению ненужных смещений и снизит производительность.

    За счет такого размещения виртуальные базы также вносят в свои vtables дополнительные элементы: vcall смещение (чтобы получить адрес конечного переопределения при переходе от указателя на виртуальную базу внутри полного объекта к началу класса, который переопределяет виртуальную функцию) для каждой определенной там виртуальной функции.Также каждая виртуальная база добавляет vbase смещения, которые вставляются в виртуальную таблицу производного класса;они позволяют найти, где начинаются данные виртуальной базы (предварительно скомпилировать их невозможно, поскольку фактический адрес зависит от иерархии:виртуальные базы находятся в конце объекта, а сдвиг от начала зависит от того, сколько невиртуальных классов наследует текущий класс.).

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

Другие советы

  1. Мне это кажется правильным.Это не так, как если бы вы использовали указатель A, вам нужно только то, что предоставляет A, плюс, возможно, реализации функций B, доступные из виртуальной таблицы A (их может быть несколько виртуальных таблиц, в зависимости от сложности компилятора и иерархии).
  2. Я бы сказал да, но это зависит от реализации компилятора, поэтому вам не обязательно об этом знать.
  3. и 4.Читайте дальше.

Я бы рекомендовал прочитать Множественное наследование считается полезным Это длинная статья, но она проясняет предмет, поскольку очень подробно объясняет, как работает наследование в C++ (ссылки на рисунки не работают, но они доступны внизу страницы).

  1. Если объект B наследует от A, то представление памяти для B будет следующим:

    • указатель на виртуальную таблицу A
    • Конкретные переменные/функции
    • указатель на виртуальную таблицу B
    • B специальные переменные/функции/переопределения

    Если у вас есть B* b = new B();(A)b->f() тогда:

    • если f была объявлена ​​как виртуальная функция, то вызывается реализация B, потому что b имеет тип B
    • если f не была объявлена ​​как виртуальная функция, то при вызове не будет поиска в vtable правильной реализации, и будет вызвана реализация A.
  2. У каждого объекта будет своя виртуальная таблица (не принимайте это как должное, так как мне нужно это изучить).

  3. Взгляни на этот пример расположения vtable при работе с множественным наследованием

  4. Видеть этот за обсуждение наследования алмазов и представления vtable

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top