Когда именно указатель виртуальной таблицы (в C ++) устанавливается для объекта? - PullRequest
8 голосов
/ 29 октября 2011

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

Мой вопрос таков: где именно в процессе создания экземпляра устанавливается этот vptr? Это назначение vptr происходит внутри конструктора объекта до / после конструктора?

Ответы [ 5 ]

10 голосов
/ 29 октября 2011

Это строго зависит от реализации.

Для большинства компиляторов

Компилятор инициализирует это -> __vptr в списке инициализатора элемента каждого конструктора.

Идея состоит в том, чтобы v-указатель каждого объекта указывал на v-таблицу своего класса, и компилятор генерирует скрытый код для этого и добавляет его в код конструктора.Что-то вроде:

Base::Base(...arbitrary params...)
   : __vptr(&Base::__vtable[0])  ← supplied by the compiler, hidden from the programmer
 {

 }

Этот C ++ FAQ объясняет суть того, что именно происходит.

8 голосов
/ 29 октября 2011

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

Хотя вы и будете читать от многих людей, что это определяется реализацией, поскольку это полный выбор vtables, но факт заключается в том, что все компиляторы используют vtables, и как только вы выбираете подход vtable, стандарт требует, чтобы тип объекта времени выполнения - это тип выполняемого конструктора / деструктора , и это, в свою очередь, означает, что, какой бы ни был механизм динамической диспетчеризации, он должен быть отрегулирован по мере прохождения цепочки строительства / разрушения. *

Рассмотрим следующий фрагмент кода:

#include <iostream>

struct base;
void callback( base const & b );
struct base {
   base() { callback( *this ); }
   ~base() { callback( *this ); }
   virtual void f() const { std::cout << "base" << std::endl; }
};
struct derived : base {
   derived() { callback( *this ); }
   ~derived() { callback( *this ); }
   virtual void f() const { std::cout << "derived" << std::endl; }
};
void callback( base const & b ) {
   b.f();
}
int main() {
   derived d;
}

Стандарт предписывает, что выходные данные этой программы равны base, derived, derived, base, но вызов в callback одинаков для всех четырех вызовов функции. Единственный способ, которым это может быть реализовано, - это обновить vptr в объекте в процессе строительства / разрушения.

1 голос
/ 24 марта 2014

Эта статья MSDN объясняет это в большой детали

Там написано:

"И окончательный ответ ... как и следовало ожидать. Это происходит в конструкторе."

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


Но будьте осторожны, допустим, у вас есть класс A и класс A1, производные от A.

  • Если вы создаете новый объект A, vptr будет установлен в самом начале конструктора класса A
  • Но если вы создадите новый объект A1:

"Вот вся последовательность событий при создании экземпляра класса A1:

  1. A1 :: A1 вызывает A :: A
  2. A :: A устанавливает vtable на vtable A
  3. A :: A выполняется и возвращает
  4. A1 :: A1 устанавливает vtable равным vtable A1
  5. A1 :: A1 выполняется и возвращает "
0 голосов
/ 29 октября 2011

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

Обратите внимание, что виртуальные функции, вызываемые вctor - это те, которые определены в как класс конструктора , а не те, которые могут быть переопределены более производным классом.

#include <iostream>

struct A
{
    A() { foo (); }
    virtual void foo () { std::cout << "A::foo" << std::endl; }
};

struct B : public A
{
    virtual void foo () { std::cout << "B::foo" << std::endl; }
};


int
main ()
{
    B b;      // prints "A::foo"
    b.foo (); // prints "B::foo"
    return 0;
}
0 голосов
/ 29 октября 2011

Хотя это зависит от реализации, на самом деле это должно произойти до того, как будет вычислено тело самого конструктора, поскольку в соответствии со спецификацией C ++ (12.7 / 3) вам разрешен доступ к нестатическим методам класса через thisуказатель в теле конструктора ... поэтому виртуальная таблица должна быть настроена до вызова тела конструктора, иначе вызов методов виртуального класса через указатель this не будет работать правильно.Хотя указатель this и виртуальная таблица - это две разные вещи, тот факт, что стандарт C ++ допускает использование указателя this в теле конструктора, демонстрирует, как компилятор должен реализовывать виртуальную таблицу для совместимых со стандартами примененийуказатель this для правильной работы, по крайней мере, с точки зрения синхронизации.Если бы vtable инициализировался во время или после вызова тела конструктора, то использование указателя this для вызова виртуальных функций внутри тела конструктора или передачи указателя this на функции, которые зависят от динамической диспетчеризации, было бы проблематичным и создавало бынеопределенное поведение.

...