2012-02-16 4 views
1

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

Например: В следующей программе производный класс унаследовал 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 строит отдельную виртуальную таблицу для каждого базового класса, унаследованного в производном классе.

+2

Поскольку виртуальные таблицы деталь реализации компилятора, вы должны сказать нам для компилятора вы хотите эту информацию. – PlasmaHH

ответ

0

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


The 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 для каждого класса.

Хорошо читать:
What happens in the hardware when I call a virtual function? How many layers of indirection are there? How much overhead is there?

+0

Спасибо, ссылка дает мне полное представление о том, как виртуальные функции обрабатываются complier. Поскольку это полностью связано с компилятором, я не смог найти ссылку на то, как компилятор обрабатывает виртуальные функции. Мне было просто интересно узнать, есть ли какой-либо сайт или книга, которая объясняет как на языке C++, так и на компиляторе. Было бы очень полезно. – user1099327

+0

@ user1099327: Я не знаю ни одной книги, которая объяснит семантику с точки зрения конкретного компилятора. Лучше всего искать документацию этого конкретного компилятора, вы могли бы также искать в Интернете. Что касается семантики языка, то существует несколько [хороших книг] (http: // stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list), но, конечно же, C++ Standard является основным источником. –

0

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

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

0

Ответ приходит из двух помещений:

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

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

1

я могу что-то не хватает, но это не так:

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; 

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

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

== == редактировать

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

+0

Адрес vtable, по крайней мере, первичный, обычно '& d' без указанных методов. Поиск других таблиц специфичен для реализации, но последняя строка, приведенная здесь, по-прежнему не будет делать этого. – ssube

+0

Вы правы ... –

Смежные вопросы