Подтвердить что ты не робот

Когда vtable создается в С++?

Когда именно компилятор создает таблицу виртуальных функций?

1), когда класс содержит хотя бы одну виртуальную функцию.

ИЛИ

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

ИЛИ

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

Связанный с этим вопрос: Можно ли отказаться от динамической отправки в иерархии С++?

например. рассмотрим следующий пример.

#include <iostream>
using namespace std;
class A {
public:
  virtual void f();
};
class B: public A {
public:
  void f();
};
class C: public B {
public:
  void f();
};

Какие классы будут содержать V-таблицу?

Так как B не объявляет f() как виртуальный, то класс C получает динамический полиморфизм?

4b9b3361

Ответ 1

Beyond "vtables являются специфичными для реализации" (которые они есть), если используется vtable: будут уникальные vtables для каждого из ваших классов. Несмотря на то, что B:: f и C:: f не объявлены виртуальными, потому что существует сопоставление подписи виртуального метода из базового класса (A в вашем коде), B:: f и C:: f являются неявно виртуальными, Поскольку каждый класс имеет по крайней мере один уникальный виртуальный метод (B:: f переопределяет A:: f для экземпляров B и C:: f аналогично для экземпляров C), вам нужны три vtables.

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

struct B {
  virtual void f() {}
  virtual void g() {}
};

struct D : B {
  virtual void f() { // would be implicitly virtual even if not declared virtual
    B::f();
    // do D-specific stuff
  }
  virtual void g() {}
};

int main() {
  {
    B b; b.g(); b.B::g(); // both call B::g
  }
  {
    D d;
    B& b = d;
    b.g(); // calls D::g
    b.B::g(); // calls B::g

    b.D::g(); // not allowed
    d.D::g(); // calls D::g

    void (B::*p)() = &B::g;
    (b.*p)(); // calls D::g
    // calls through a function pointer always use virtual dispatch
    // (if the pointed-to function is virtual)
  }
  return 0;
}

Некоторые конкретные правила, которые могут помочь; но не цитируйте меня на них, я, вероятно, пропустил некоторые случаи:

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

Помните, что vtable похожа на статический член данных класса, а экземпляры имеют только указатели на них.

Также см. всеобъемлющую статью С++: Under the Hood (март 1994 г.) Ян Грей. (Попробуйте Google, если эта ссылка умирает.)

Пример повторного использования таблицы vtable:

struct B {
  virtual void f();
};
struct D : B {
  // does not override B::f
  // does not have other virtuals of its own
  void g(); // still might have its own non-virtuals
  int n; // and data members
};

В частности, уведомление B dtor не является виртуальным (и это вероятно, ошибка в реальном коде), но в этом примере экземпляры D указывают на ту же таблицу vtable, что и в экземплярах B.

Ответ 2

Ответ: "это зависит". Это зависит от того, что вы подразумеваете под "содержать vtbl", и это зависит от решений, сделанных разработчиком конкретного компилятора.

Строго говоря, no 'class' никогда не содержит таблицу виртуальных функций. Некоторые экземпляры некоторых классов содержат указатели на таблицы виртуальных функций. Тем не менее, это всего лишь одна возможная реализация семантики.

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

Если вы спросите: "Что делает GCC?" или "Что делает Visual С++?" то вы можете получить конкретный ответ.

@Ответ Хассана Сайда, вероятно, ближе к тому, о чем вы спрашивали, но очень важно сохранить эти понятия прямо здесь.

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

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

Реализация, компилятор разрешает делать то, что хочет выполнить этот результат.

Ответ 3

Ответ

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

Читайте дальше для обсуждения и тестов

- объяснение -

Когда вы указываете функцию-член как виртуальную, существует вероятность, что вы можете попытаться использовать подклассы с помощью базового класса по полиморфно во время выполнения. Чтобы поддерживать С++ гарантию производительности по языковому дизайну, они предложили самую легкую возможную стратегию реализации, то есть один уровень косвенности, и только тогда, когда класс может использоваться полиморфно во время выполнения, а программист определяет это, устанавливая хотя бы одну функцию виртуальные.

Вы не берете на себя расходы на vtable, если избегаете ключевого слова virtual.

- изменить: отразить ваше редактирование -

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

В вашем примере все три класса будут иметь vtable, потому что вы можете попробовать использовать все три класса с помощью A*.

- тест - GCC 4+ -

#include <iostream>

class test_base
{
  public:
    void x(){std::cout << "test_base" << "\n"; };
};

class test_sub : public test_base
{
public:
  virtual void x(){std::cout << "test_sub" << "\n"; } ;
};

class test_subby : public test_sub
{
public:
  void x() { std::cout << "test_subby" << "\n"; }
};

int main() 
{
  test_sub sub;
  test_base base;
  test_subby subby;

  test_sub * psub;
  test_base *pbase;
  test_subby * psubby;

  pbase = &sub;
  pbase->x();
  psub = &subby;
  psub->x();

  return 0;
}

Выход

test_base
test_subby

test_base не имеет виртуальной таблицы, поэтому любое нажатие на нее будет использовать x() from test_base. test_sub, с другой стороны, изменяет характер x(), и его указатель будет косвенным через vtable, и это будет показано test_subby x().

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

Ответ 4

Вы приложили все усилия, чтобы сделать свой вопрос очень ясным и точным, но все еще немного информации отсутствует. Вероятно, вы знаете, что в реализациях, использующих V-Table, сама таблица обычно является независимой структурой данных, хранящейся вне полиморфных объектов, тогда как сами объекты сохраняют неявный указатель на таблицу. Итак, о чем вы спрашиваете? Может быть:

  • Когда объект получает неявный указатель на V-таблицу, вставленную в него?

или

  • Когда выделенная отдельная V-таблица создана для заданного типа в иерархии?

Ответ на первый вопрос: объект получает неявный указатель на V-таблицу, вставленную в него, когда объект имеет тип полиморфного класса. Тип класса является полиморфным, если он содержит хотя бы одну виртуальную функцию, или любой из ее прямых или косвенных родителей является полиморфным (это ответ 3 из вашего набора). Также обратите внимание, что в случае множественного наследования объект может (и будет) содержать в себе множество указателей V-Table, встроенных в него.

Ответ на второй вопрос может быть таким же, как и для первого (вариант 3), с возможным исключением. Если какой-либо полиморфный класс в иерархии единого наследования не имеет собственных виртуальных функций (нет новых виртуальных функций, нет переопределений для родительской виртуальной функции), возможно, что реализация может решить не создавать отдельный V-Table для этого класса, а вместо этого используйте для этого класса непосредственный родительский V-столбец (так как в любом случае он будет одинаковым). То есть в этом случае оба объекта родительского типа и объекты производного типа будут хранить одно и то же значение в своих встроенных указателях V-Table. Это, конечно, сильно зависит от реализации. Я проверил GCC и MS VS 2005, и они не действуют таким образом. Они оба создают отдельную V-таблицу для производного класса в этой ситуации, но я, кажется, вспоминаю слух о реализациях, которые этого не делают.

Ответ 5

Стандарты С++ не позволяют использовать V-таблицы для создания иллюзии полиморфных классов. В большинстве случаев реализации используют V-таблицы, чтобы хранить дополнительную информацию. Короче говоря, эти дополнительные части информации оснащены, когда у вас есть хотя бы одна виртуальная функция.

Ответ 6

Поведение определено в параграфе 2 раздела 10.3 спецификации языка С++:

Если виртуальная функция-член vf объявленной в базе классов и в класс Производные, полученные непосредственно или косвенно из базы, члена функция vf с тем же именем и тот же список параметров, что и Base:: vf объявлен, тогда Derived:: vf также виртуальный (независимо от того, так ли это объявлено), и он переопределяет Base:: vf.

Обозначил соответствующую фразу. Таким образом, если ваш компилятор создает v-таблицы в обычном смысле, тогда все классы будут иметь v-таблицу, поскольку все их методы f() являются виртуальными.