Механизм вызова виртуальных функций - PullRequest
0 голосов
/ 27 сентября 2011
class base {
public:
  virtual void fn(){}
};


class der: public base {
public:
  void fn(){}
};

der d;

base *b = &d;
b->fn();

Когда компилятор встречает оператор b->fn(), компилятору доступна следующая информация:

  1. b - указатель на базу классов,
  2. Базовый класс имеет виртуальную функцию, а также vptr.

Мой вопрос: как vptr класса der входит в изображение во время выполнения?

Ответы [ 6 ]

4 голосов
/ 27 сентября 2011

Священному Стандарту не требуется таблица vptr или vptr. Однако на практике это единственный способ реализовать это.

Итак, вот псевдокод того, что происходит:

  1. a_base_compatible_vtable_ptr = b->__vtable_ptr__
  2. a_func_ptr = a_base_compatible_vtable_ptr[INDEX_FOR_fn]
  3. a_func_ptr( b )

Основная идея заключается в том, что для объекта класса der указатель vtable в объекте будет указывать на класс der & rsquo; vtable, который совместим с классом base & rsquo; vtable, но содержит указатели, указывающие на класс der & rsquo; реализации функций.

Таким образом, вызывается реализация функции der.

На практике аргумент указателя this, передаваемый в точку (3), обычно оптимизируется специальным образом, передавая указатель this в регистр выделенного процессора вместо стека машин.

Более подробное обсуждение см. В литературе по модели памяти C ++, например, Книга Стэнли Липпмана Внутри объектной модели C ++ .

Приветствия и hth.,

3 голосов
/ 27 сентября 2011

Рассуждая об этом, я помогаю сохранять четкое представление о расположении классов в памяти и, в частности, о том, что объект der содержит подобъект base, который имеетточно такой же макет памяти, как и у любого другого объекта base.

В частности, ваш макет объекта base будет просто содержать указатель на vtable (нет полей) и подобъект base объекта der также будет содержать этот указатель, отличается только значение, хранящееся в указателе, и он будет ссылаться на der версию base vtable (чтобы сделать его немного более интересным, учтите, что и base, и der содержит элементы):

// Base object         // base vtable (ignoring type info)
+-------------+        +-----------+
| base::vptr  |------> | &base::fn |
+-------------+        +-----------+
| base fields |
+-------------+

// Derived object      // der vtable
+-------------+        +-----------+
| base::vptr  |------> | &der::fn  |
+-------------+        +-----------+ 
| base fields |
+-------------+ <----- [ base subobject ends here ]
| der fields  |
+-------------+

Если вы посмотрите на два рисунка, вы можете распознать подобъект base в объекте der, когда вы делаете base *bp = &d;, то, что вы делаете, получаетуказатель на base подобъект внутри der.В этом случае область памяти подобъекта base точно такая же, как и у подобъекта base, но это не обязательно должно быть так.Важно то, что указатель будет ссылаться на подобъект base, а указанная память имеет макет памяти base, но с той разницей, что указатели, хранящиеся в объекте, будут ссылаться на derверсии vtable.

Когда компилятор увидит код bp->fn(), он будет считать его объектом base и знает, где находится vptr в base object, и он также знает, что fn является первой записью в vtable, поэтому ему нужно только сгенерировать код для bp->vptr[ 0 ]().Если bp относится к base объекту, тогда bp->vptr будет ссылаться на base vtable , а bp->vptr[0] будет base::fn.Если указатель с другой стороны ссылается на объект der, то bp->vptr будет ссылаться на der vtable, а bp->vptr[0] будет ссылаться на der::fn.

Обратите внимание, что во время компиляциисгенерированный код для обоих случаев абсолютно одинаков: bp->vptr[0]() и что он отправляется различным функциям на основе данных, хранящихся в объекте base (sub), в частности значения, хранящегося в vptr, который получаетобновлено в конструкции.

Четко акцентируя внимание на том факте, что подобъект base должен присутствовать и совместим с объектом base, вы можете рассматривать более сложные сценарии как множественное наследование:

struct data { 
   int x;
};
class other : public data, public base {
   int y;
public:
   virtual void fn() {}
};
+-------------+
| data::x     |
+-------------+ <----- [ base subobject starts here ] 
| base::vptr  |
+-------------+
| base fields |
+-------------+ <----- [ base subobject ends here ]
| other::y    |
+-------------+
int main() {
   other o;
   base *bp = o;
}

Это более интересный случай, когда есть другая база, в этот момент вызов base * bp = o; создает указатель на подобъект base и может быть проверен, чтобы указывать на другое местоположение, чемобъект o (попробуйте распечатать значения &o и bp).С вызывающего сайта это не имеет большого значения, потому что bp имеет статический тип base*, и компилятор всегда может разыменовать этот указатель, чтобы найти base::vptr, используйте его, чтобы найти fn в vtable и в итоге вызвать other::fn.

В этом примере происходит немного больше магии, так как подобъекты other и base не выровнены перед вызовом действительной функции other::fn, указателя thisдолжен быть скорректирован.Компилятор решает проблему, сохраняя не указатель на other::fn в other vtable , а скорее указатель на virtual thunk (небольшой фрагмент кода, который фиксирует значение this и переадресация вызова на other::fn)

1 голос
/ 18 декабря 2012

я нашел лучший ответ за Здесь ..

1 голос
/ 27 сентября 2011

В типичной реализации есть только один vptr на объект.Если объект имеет тип der, этот указатель будет указывать на der vtable, а если он имеет тип base, то он указывает на base vtable.Этот указатель устанавливается при создании.Нечто похожее на это:

class base {
public:
  base() {
    vptr = &vtable_base;
  }
  virtual void fn(){}
protected:
   vtable* vptr;
};


class der: public base {
public:
  der() {
    vptr = &vtable_der;
  }
  void fn(){}
};

Вызов b->fn() делает что-то похожее на:

vtable* vptr = b->vptr;
void (*fn_ptr)() = vtpr[fn_index];
fn_ptr(b);
0 голосов
/ 27 сентября 2011

vptr не "класса", но "экземпляра объекта".Когда создается d, сначала выделяется пространство для него (и также содержит vptr).

Затем создается base (и vptr делается указывающим на базовый vtable), а затем der построен вокруг base, а vptr обновляется, чтобы указывать на der vtable.

В обеих таблицах base и der есть записи для функций fn(), и, поскольку vptrобратитесь к одному из них, когда вы вызываете b-> fn (), фактически происходит вызов vptr(p)[fn_index]().

Но так как vptr (p) == vptr (& d), вв этом случае вызов der::fn будет результатом.

0 голосов
/ 27 сентября 2011

Vptr - это свойство объекта, а не указателя. Поэтому статический тип b (base) не имеет значения. Что имеет значение, так это его динамический тип (der). Объект, на который указывает b, имеет свой vptr, указывающий на таблицу виртуальных методов der (vtbl).

Когда вы вызываете b->fn(), это таблица виртуальных методов der, к которой обращаются, чтобы выяснить, какой метод вызывать.

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