Отличается ли расположение виртуального указателя в объекте, если объект имеет полиморфизм, по сравнению с множественным наследованием?

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


Полиморфизм
Виртуальный указатель находится в верхней части объекта и только для этого класса, то есть существует только один виртуальный указатель. Например:

class A {
    public:
        virtual void walk();
}

class B : A {
    public:
        int num;

        virtual void walk();
        virtual void run();
}

Тогда объект в памяти будет выглядеть так:

| vPointer to class B vTable |
| int num                    |



Множественное наследование
Существует несколько виртуальных указателей, по одному для каждого класса. Однако виртуальные таблицы для этих классов изменены таким образом, что методы перезаписываются и направляются по адресу текущего функционального кода классов. Однако я думаю, что это будет означать, что каждый класс потенциально может иметь несколько разных vTables. Например:

class A {
    public:
        virtual void walk();
}

class B {
    public: 
        char name;            

        virtual void run();
}

class C : A, B {
    public:
        int num;

        virtual void run();
        virtual void walk();
        virtual void swim();
}

Тогда объект в памяти будет выглядеть так:

| vPointer to class A vTable |
| vPointer to class B vTable |
| char name                  |
| int num                    |

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

Любая помощь приветствуется.

Спасибо.


person Cail Demetri    schedule 15.11.2014    source источник
comment
Спецификация языка C++ не требует какой-либо конкретной реализации виртуальной диспетчеризации. Это зависит от реализации.   -  person Raymond Chen    schedule 15.11.2014


Ответы (1)


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

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

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

Полиморфизм с одиночным наследованием

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

Указатель vtable хранится в начале объекта, потому что это наиболее эффективный способ гарантировать, что виртуальная функция может быть вызвана эффективно, не зная точного расположения объекта, на который он указывает:

A a; B b;    // example assumes that class B inherits A publicly 
A *p = &b;   // pointer to a base object for showing polymorphism in action   
...
p->walk();   // at this stage, you don't know if p points to A or B.  
             // Bus as vtable pointer ist at the begin of the object, you don't have to care

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

Полиморфизм с множественным наследованием

Множественное наследование означает, что ваш производный объект C имеет два подобъекта, один для A и один для B. Каждый из подобъектов должен управлять своей виртуальной таблицей, как и любой другой объект своего типа. Это означает, что есть две vtables, каждая в начале подобъекта:

| vPointer to class A vTable |
| data for A object          |   => here it's empty
| vPointer to class B vTable |
| char name                  |   => here the data for the b object 
| int num                    | 

Это необходимо, потому что у вас может быть такой код:

C c; 
A *pa = &c; B *pb = &c;
pa->walk(); pb->run(); 

Но производный класс C является собственным классом, и в нем также определена виртуальная функция:

C *pc = &c;
pc->walk(); pc->run(); pc->swim(); 

Это означает, что для D также существует виртуальная таблица. Где она хранится? Он должен быть в начале объекта. Таким образом, vtable для C будет надмножеством vtable для A:

| vPointer to class C vTable |   => the first functions in the table are those of A, followed by all the virtual functions of C.  
| data for A object          |   => here it's empty
| vPointer to class B vTable |
| char name                  |   => here the data for the b object 
| int num                    | 

Вот ассемблер, сгенерированный MSVC2013 для vtables:

CONST   SEGMENT
??_7C@@6BB@@@ DD FLAT:??_R4C@@6BB@@@            ; C::`vftable' loaded at begin of B object
    DD  FLAT:?run@B@@UAEXXZ                       ; this is in fact the vtable for B
CONST   ENDS
CONST   SEGMENT
??_7C@@6BA@@@ DD FLAT:??_R4C@@6BA@@@            ; C::`vftable' loaded at begin of C object
    DD  FLAT:?walk@A@@UAEXXZ                      ;  that's the subset for A 
    DD  FLAT:?swim@C@@UAEXXZ                      ;  that's the superset for C specific gunctions
CONST   ENDS
person Christophe    schedule 29.11.2014