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

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

Вопрос

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

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

Решение

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

Это не является частью стандарта, поэтому любая реализация может делать все, что хочет, но общее практическое правило заключается в том, что в реализации, использующей указатели виртуальных таблиц, в качестве нулевого приближения для динамической диспетчеризации вам потребуется не болеемного указателей на виртуальные таблицы, так как есть классы, которые добавляют в иерархию новый виртуальный метод.(В некоторых случаях виртуальная таблица может быть расширена, а базовый и производный типы совместно используют один 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 будет расширенной версиейвиртуальная таблица для b с дополнительными элементами в конце, поддерживающая двоичную совместимость (т. е. виртуальная таблица d может интерпретироваться как виртуальная таблица b для доступа к методам, доступным в b), а объект d будет иметь один vptr.

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

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

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

Мелкий шрифт

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

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

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

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

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

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

struct B { 
    virtual ~B(); 
};

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

Здесь у B есть vptr, поэтому D не получает свой собственный vptr, он повторно использует существующий vptr;виртуальная таблица B расширена записью для foo(). Виртуальная таблица для D "производна" от виртуальной таблицы для 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 своего виртуального базового класса (но может в особом случае).

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