Сколько vptr будет иметь объект класса (использует одиночное / множественное наследование)? - PullRequest
6 голосов
/ 27 июля 2010

Сколько vptrs обычно требуется для объекта, у которого clas (child) имеет одиночное наследование с базовым классом, который множественно наследует base1 и base2.Какова стратегия для определения того, сколько vptrs объект предоставил, у него есть пара одиночного наследования и множественное наследование.Хотя стандарт не определяет vptrs, я просто хочу узнать, как реализация реализует виртуальную функцию.

Ответы [ 2 ]

5 голосов
/ 27 июля 2010

Почему тебя это волнует? Простой ответ: достаточно , но я думаю, вы хотите что-то более полное.

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

// some examples:
struct a { void foo(); };           // no need for virtual table
struct b : a { virtual foo1(); };   // need vtable, and vptr
struct c : b { void bar(); };       // no extra virtual table, 1 vptr (b) suffices
struct d : b { virtual bar(); };    // extra vtable, need b.vptr and d.vptr

struct e : d, b {};                 // 3 vptr, 2 for the d subobject and one for
                                    // the additional b
struct f : virtual b {};
struct g : virtual b {};
struct h : f, g {};                 // single vptr, only b needs vtable and
                                    // there is a single b

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

В действительности компиляторы объединяют разные таблицы в одну таблицу. Когда d добавляет новую виртуальную функцию поверх набора функций в b, компилятор объединит потенциальные две таблицы в одну, добавив новые слоты в конец vtable, поэтому vtable для d будет расширенной версией vtable для b с дополнительными элементами в конце, поддерживающими двоичную совместимость (т. е. vtable d можно интерпретировать как v 1015 * vtable для доступа к методам, доступным в b), а d объект будет иметь один vptr.

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

Наконец, в случае виртуального наследования вещи становятся еще более сложными, и может быть несколько vtables для одного и того же завершенного объекта с обновлением vptr по мере развития строительства / разрушения (vptr всегда обновляется по мере развития строительства / разрушения, но без Виртуальное наследование vptr будет указывать на vtables базы, тогда как в случае виртуального наследования будет несколько vtables для одного и того же типа)

4 голосов
/ 18 августа 2012

шрифт

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

Вы были предупреждены.

Расположение объекта: не виртуальное наследование

Если вы наследуете от базовых классов, и у них есть vptr, у вас, естественно, столько же унаследованных vptr в вашем классе.

Вопрос в том, когда компилятор добавит vptr в класс, у которого уже есть унаследованный vptr?

Компилятор попытается избежать добавления избыточного vptr:

struct B { 
    virtual ~B(); 
};

struct D : B { 
    virtual void foo(); 
};

Здесь B имеет vptr, поэтому D не получает свой собственный vptr, он повторно использует существующий vptr; В таблице B добавлена ​​запись для foo(). Vtable для D «получен» из vtable для B, псевдокод:

struct B_vtable { 
    typeinfo *info; // for typeid, dynamic_cast
    void (*destructor)(B*); 
};

struct D_vtable : B_vtable { 
    void (*foo)(D*); 
};

Опять мелкий шрифт: это упрощение настоящего vtable, чтобы понять.

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

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

struct B2 : virtual A {
};

Существует преобразование из B2* в A*, поэтому объект B2 должен обеспечивать эту функциональность:

  • либо с A* членом
  • либо с членом int: offset_of_A_from_B2
  • либо используя свой vptr, сохранив offset_of_A_from_B2 в vtable

Как правило, класс не повторно использует vptr своего виртуального базового класса (но это может быть в очень особом случае).

...