Построение виртуальной таблицы компилятором - PullRequest
1 голос
/ 16 февраля 2012

Когда производный класс унаследовал свойства n базовых классов, каждый с одной виртуальной функцией, при создании виртуальной таблицы для производного класса, почему компилятор создает отдельную виртуальную таблицу для каждого базового класса?Почему компилятор не создает единую виртуальную таблицу, состоящую из всех ссылок на виртуальные функции базового класса и его собственную?

Например: В следующей программе производный класс унаследовал 3 базовых класса, каждый с одной виртуальной функцией.

             class Base1
             {
                  public:
                     virtual void base1_func1(){cout<<"In Base1:base1_func1:"<<endl;} 
             };

             class Base2
             {
                  public:
                  virtual void base2_func1(){cout<<"In Base2:base2_func1:"<<endl;} 
             };

             class Base3
             {
                 public:
                 virtual void base3_func1(){cout<<"In Base3:base3_func1:"<<endl;} 
             };

            class Derived:public Base1, public Base2, public Base3
            {
            };

            typedef void (*Func) (void);

            int main()
            {
                Base1 b1;
                Base2 b2;
                Base3 b3;
                Derived d;
                Func f= NULL;

                cout<<"Size of Base1 obj:"<<sizeof(b1)<<endl;
                cout<<"Size of Base2 obj:"<<sizeof(b2)<<endl;
                cout<<"Size of Base3 obj:"<<sizeof(b3)<<endl;
                cout<<"Size of Derived obj:"<<sizeof(d)<<endl;
                cout<<"Printing the VPTR Address of Base1 obj b1 :"<< *((int *)(&b1)+0)<<endl;
                cout<<"Printing the Address of Base1 func1 in VTABLE:"<< (int *)*((int            *)*((int *)(&b1)+0)+0)<<endl;
               f = (Func)*((int *)*((int *)(&b1)+0)+0);
               f();
               cout<<"Printing the VPTR Address of Base2 obj b2 :"<< *((int *)(&b2)+0)<<endl;
               cout<<"Printing the Address of Base2 func1 in VTABLE:"<< (int *)*((int *)*((int *)(&b2)+0)+0)<<endl;
               f = (Func)*((int *)*((int *)(&b2)+0)+0);
               f();
               cout<<"Printing the VPTR Address of Base3 obj b3 :"<< *((int *)(&b3)+0)<<endl;
               cout<<"Printing the Address of Base3 func1 in VTABLE:"<< (int *)*((int *)*((int *)(&b3)+0)+0)<<endl;
               f = (Func)*((int *)*((int *)(&b3)+0)+0);
               f();

               cout<<"Printing the VPTR1 Address of Derived obj d :"<< *((int *)(&d)+0)<<endl;
               cout<<"Printing the VPTR2 Address of Derived obj d :"<< *((int *)(&d)+1)<<endl;
               cout<<"Printing the VPTR3 Address of Derived obj d :"<< *((int *)(&d)+2)<<endl;
               cout<<"Printing the Address of Derived base1_func1 in VTABLE:"<< (int *)*((int *)*((int *)(&d)+0)+0)<<endl;
               f = (Func)*((int *)*((int *)(&d)+0)+0);
               f();
              cout<<"Printing the Address of Derived base2_func1 in VTABLE:"<< (int *)*((int *)*((int *)(&d)+1)+0)<<endl;
              f = (Func)*((int *)*((int *)(&d)+1)+0);
              f();
              cout<<"Printing the Address of Derived base3_func1 in VTABLE:"<< (int *)*((int *)*((int *)(&d)+2)+0)<<endl;
             f = (Func)*((int *)*((int *)(&d)+2)+0);
             f();

            return 0;
         }

         Output:
         Size of Base1 obj:4
         Size of Base2 obj:4
         Size of Base3 obj:4
         Size of Derived obj:12
         Printing the VPTR Address of Base1 obj b1 :134517392
         Printing the Address of Base1 func1 in VTABLE:0x8048dfe
         In Base1:base1_func1:
         Printing the VPTR Address of Base2 obj b2 :134517424
         Printing the Address of Base2 func1 in VTABLE:0x8048e2a
         In Base2:base2_func1:
         Printing the VPTR Address of Base3 obj b3 :134517456
         Printing the Address of Base3 func1 in VTABLE:0x8048e56
         In Base3:base3_func1:
         Printing the VPTR1 Address of Derived obj d :134517512
        Printing the VPTR2 Address of Derived obj d :134517524
        Printing the VPTR3 Address of Derived obj d :134517536
        Printing the Address of Derived base1_func1 in VTABLE:0x8048dfe
        In Base1:base1_func1:
        Printing the Address of Derived base2_func1 in VTABLE:0x8048e2a
        In Base2:base2_func1:
        Printing the Address of Derived base3_func1 in VTABLE:0x8048e56
        In Base3:base3_func1:

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

Ответы [ 4 ]

1 голос
/ 16 февраля 2012

Я могу что-то упустить, но это не так:

cout<<"Printing the VPTR1 Address of Derived obj d :"<< *((int *)(&d)+0)<<endl;
cout<<"Printing the VPTR2 Address of Derived obj d :"<< *((int *)(&d)+1)<<endl;
cout<<"Printing the VPTR3 Address of Derived obj d :"<< *((int *)(&d)+2)<<endl;

Вы просто печатаете адрес элемента "X" d.&d = адрес d (&d + X) = продолжить до элемента X или, другими словами + (X * sizeof(d)) (int *)(&d + X) = посмотреть на этот адрес как указатель на int (вместо указателя на d) *((int *)(&d + 2) = getзначение int (в основном значение адреса).

Я говорю, что если вы добавите к Derived классу больше закрытых членов и тем самым увеличите sizeof(d), вы получите другие значения, но ясно, что VPTR не двигался.

== edit ==

Не уверен, как это сделать, но вам нужно найти правильный способ найти адрес VPTR

0 голосов
/ 16 февраля 2012

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

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

0 голосов
/ 16 февраля 2012

Ответ приходит из двух предпосылок:

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

Комбинацияусловия подразумевают, что вы не можете расширить виртуальную таблицу, добавив только новые указатели на функции для разных баз, поскольку при получении указателя на каждую базу она будет ожидать, что ее vptr ссылается на совместимую виртуальную таблицу и включает в себя информацию о типе.Обратите внимание, что по крайней мере теоретически вы можете расширить ту же таблицу vtable для получения классов, просто добавив указатели на функции, поскольку производный тип может повторно использовать указатель RTTI из первой базы.

0 голосов
/ 16 февраля 2012

Примечание: Ответ на этот Вопрос является чисто специфическим для реализации, стандарт C ++ даже не упоминает виртуальную таблицу или виртуальные указатели, поэтому компиляторы могут свободно реализовывать динамическую диспетчеризацию с любым механизмом, стандарт определяет ожидаемое поведение, и пока компиляторы удовлетворяют этим требованиям, они могут осуществлять динамическую диспетчеризацию с использованием любого механизма, который они выберут.
Сказав вышесказанное, все известные компиляторы реализуют виртуальный механизм, используя механизмы vtable и vptr.


vtable - это таблица, в которой хранятся адреса каждого virtual метода этого класса или классов, из которых он получен.
vptr указывает на vtable этого класса.
vptr похоронен где-то в указателе this.

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

Например:
Рассмотрим следующую иерархию классов:

class shape
{
  public:
  virtual void Draw1();
  virtual void Draw2();
  virtual void Draw3();
};

vtable для этого класса выглядит так:

Base class V-table

Рассмотрим производный класс:

class line: public shape
{
  public:
  virtual void Draw1();
  virtual void Draw2();
};

vtable для этого класса будет:

Derived class vtable

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

Таким образом, чтобы динамическая диспетчеризация работала, компилятору необходимо создать отдельные vtable для каждого класса.

Хорошо читать:
Что происходит в оборудовании, когда я вызываю виртуальную функцию? Сколько слоев косвенности есть? Сколько там накладных расходов?

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