Когда создается виртуальная таблица в C ++?

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

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

ИЛИ

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

ИЛИ

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

С этим связан вопрос: ИЛИ 2) когда непосредственный базовый класс содержит хотя бы один виртуальный ...

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

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

ИЛИ

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

ИЛИ

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

С этим связан вопрос: ИЛИ 2) когда непосредственный базовый класс содержит хотя бы один виртуальный ...

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

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

ИЛИ

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

ИЛИ

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

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

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

#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 получить динамический полиморфизм?

24
задан Aquarius_Girl 5 November 2014 в 05:48
поделиться

6 ответов

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

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

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 похож на статический член данных класса, и экземпляры имеют только указатели на них.

Также смотрите исчерпывающую статью C++: Под капюшоном (март 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
};

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

23
ответ дан 28 November 2019 в 23:28
поделиться

Ответ

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

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

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

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

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

-- edit : чтобы отразить Вашу редакцию --

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

В вашем примере все три класса будут иметь таблицу, это потому что вы можете попробовать использовать все три класса через 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;
}

output

test_base
test_subby

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

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

.
7
ответ дан 28 November 2019 в 23:28
поделиться

Ответ: "Это зависит". Он зависит от того, что вы имеете в виду под 'contain a vtbl' и зависит от решений, принятых реализацией конкретного компилятора.

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

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

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

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

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

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

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

.
8
ответ дан 28 November 2019 в 23:28
поделиться

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

.
2
ответ дан 28 November 2019 в 23:28
поделиться

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

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

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

.
1
ответ дан 28 November 2019 в 23:28
поделиться

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

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

или

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

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

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

.
3
ответ дан 28 November 2019 в 23:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: