Каждый объект виртуального класса имеют указатель на vtable?

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

Так что думайте о человеческом теле как о классе. Поскольку экземпляры объекта могут называться разными вещами, такими как Джон или Кайл, при обращении к типичному человеку в методе, который вы использовали бы, это. Например, чтобы получить сердцебиение для любого человека, у вас должно быть что-то вроде этого. GetHeartbeat ().

Надеюсь, это поможет вам осмыслить это.

12
задан MainID 18 February 2009 в 15:47
поделиться

9 ответов

Все классы с виртуальным методом будут иметь сингл vtable, который совместно используется всеми объектами класса.

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

Обратите внимание, что ни одно из этого не получает мандат языком C++ - реализация может обработать виртуальную отправку некоторый другой путь, если это хочет. Однако это - реализация, которая используется каждым компилятором, с которым я знаком. Книга Stan Lippman, "В Модели Объекта C++" описывает, как это работает очень приятно.

15
ответ дан 2 December 2019 в 03:43
поделиться

Как кто-то еще сказал, Стандарт C++ не передает под мандат таблицу виртуальных методов, но позволяет использоваться. Я сделал свои тесты с помощью gcc и этот код и один из самого простого сценария:

class Base {
public: 
    virtual void bark() { }
    int dont_do_ebo;
};

class Derived1 : public Base {
public:
    virtual void bark() { }
    int dont_do_ebo;
};

class Derived2 : public Base {
public:
    virtual void smile() { }
    int dont_do_ebo;
};

void use(Base* );

int main() {
    Base * b = new Derived1;
    use(b);

    Base * b1 = new Derived2;
    use(b1);
}

Добавленные элементы данных для предотвращения компилятора, чтобы дать базовому классу размер - нуля (это известно как empty-base-class-optimization). Это - расположение, которое выбрала GCC: (использование печати - fdump-иерархия-классов)

Vtable for Base
Base::_ZTV4Base: 3u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI4Base)
8     Base::bark

Class Base
   size=8 align=4
   base size=8 base align=4
Base (0xb7b578e8) 0
    vptr=((& Base::_ZTV4Base) + 8u)

Vtable for Derived1
Derived1::_ZTV8Derived1: 3u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI8Derived1)
8     Derived1::bark

Class Derived1
   size=12 align=4
   base size=12 base align=4
Derived1 (0xb7ad6400) 0
    vptr=((& Derived1::_ZTV8Derived1) + 8u)
  Base (0xb7b57ac8) 0
      primary-for Derived1 (0xb7ad6400)

Vtable for Derived2
Derived2::_ZTV8Derived2: 4u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI8Derived2)
8     Base::bark
12    Derived2::smile

Class Derived2
   size=12 align=4
   base size=12 base align=4
Derived2 (0xb7ad64c0) 0
    vptr=((& Derived2::_ZTV8Derived2) + 8u)
  Base (0xb7b57c30) 0
      primary-for Derived2 (0xb7ad64c0)

Поскольку Вы видите, что каждый класс имеет vtable. Первые две записи являются особенными. Второй указывает на данные RTTI класса. Первый - я знал это, но забыл. Это имеет некоторое использование в более сложных случаях. Ну, поскольку расположение показывает, если у Вас будет объект класса Derived1, затем то vptr (v-table-pointer) укажет на v-таблицу класса Derived1, конечно, который имеет точно одну запись для ее функциональной коры, указывающей на версию Derived1. vptr Derived2 указывает на vtable Derived2, который имеет две записи. Другой является новым методом, это добавляется им, улыбка. Это повторяет запись для Основы:: кора, которая укажет на версию Основы функции, конечно, потому что это - наиболее полученная версия его.

Я также вывел дерево, это сгенерировано GCC после того, как некоторая оптимизация сделана (встроенный конструктор...), с-fdump-tree-optimized. Вывод использует язык среднего конца GCC GIMPL который является фронтендом, независимым, расположенным с отступом в некоторую подобную C блочную структуру:

;; Function virtual void Base::bark() (_ZN4Base4barkEv)
virtual void Base::bark() (this)
{
<bb 2>:
  return;
}

;; Function virtual void Derived1::bark() (_ZN8Derived14barkEv)
virtual void Derived1::bark() (this)
{
<bb 2>:
  return;
}

;; Function virtual void Derived2::smile() (_ZN8Derived25smileEv)
virtual void Derived2::smile() (this)
{
<bb 2>:
  return;
}

;; Function int main() (main)
int main() ()
{
  void * D.1757;
  struct Derived2 * D.1734;
  void * D.1756;
  struct Derived1 * D.1693;

<bb 2>:
  D.1756 = operator new (12);
  D.1693 = (struct Derived1 *) D.1756;
  D.1693->D.1671._vptr.Base = &_ZTV8Derived1[2];
  use (&D.1693->D.1671);
  D.1757 = operator new (12);
  D.1734 = (struct Derived2 *) D.1757;
  D.1734->D.1682._vptr.Base = &_ZTV8Derived2[2];
  use (&D.1734->D.1682);
  return 0;    
}

Как мы видим приятно, это просто устанавливает один указатель - vptr - который укажет на соответствующее vtable, которое мы видели прежде при создании объекта. Я также вывел ассемблерный код для создания Derived1, и вызов для использования (4$ первый регистр аргумента, 2$ регистр возвращаемого значения, 0$ always-0-register), после demangling имена в нем c++filt инструмент :)

      # 1st arg: 12byte
    add     $4, $0, 12
      # allocate 12byte
    jal     operator new(unsigned long)    
      # get ptr to first function in the vtable of Derived1
    add     $3, $0, vtable for Derived1+8  
      # store that pointer at offset 0x0 of the object (vptr)
    stw     $3, $2, 0
      # 1st arg is the address of the object
    add     $4, $0, $2
    jal     use(Base*)

Что происходит, если мы хотим звонить bark?:

void doit(Base* b) {
    b->bark();
}

Код GIMPL:

;; Function void doit(Base*) (_Z4doitP4Base)
void doit(Base*) (b)
{
<bb 2>:
  OBJ_TYPE_REF(*b->_vptr.Base;b->0) (b) [tail call];
  return;
}

OBJ_TYPE_REF конструкция GIMPL, которая довольно печатается в (она документируется в gcc/tree.def в gcc SVN исходный код)

OBJ_TYPE_REF(<first arg>; <second arg> -> <third arg>)

Это означает: Используйте выражение *b->_vptr.Base на объекте b, и сохраните frontend (C++) определенное значение 0 (это - индекс в vtable). Наконец, это является передающим b как "этот" аргумент. Был бы мы вызывать функцию, которая появляется в 2-м индексе в vtable (примечание, мы не знаем, который vtable, из которых вводят!), GIMPL был бы похож на это:

OBJ_TYPE_REF(*(b->_vptr.Base + 4);b->1) (b) [tail call];

Конечно, здесь ассемблерный код снова (отключенный материал стекового фрейма):

  # load vptr into register $2 
  # (remember $4 is the address of the object, 
  #  doit's first arg)
ldw     $2, $4, 0
  # load whatever is stored there into register $2
ldw     $2, $2, 0
  # jump to that address. note that "this" is passed by $4
jalr    $2

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

12
ответ дан 2 December 2019 в 03:43
поделиться

Vtable на экземпляр класса, т.е. если у меня есть 10 объектов класса, который имеет виртуальный метод существует только один vtable, который является общим для все 10 объектов.

Все 10 объектов в этом случае указывают на то же vtable.

4
ответ дан 2 December 2019 в 03:43
поделиться

Попробуйте это дома:

#include <iostream>
struct non_virtual {}; 
struct has_virtual { virtual void nop() {} }; 
struct has_virtual_d : public has_virtual { virtual void nop() {} }; 

int main(int argc, char* argv[])
{
   std::cout << sizeof non_virtual << "\n" 
             << sizeof has_virtual << "\n" 
             << sizeof has_virtual_d << "\n";
}
4
ответ дан 2 December 2019 в 03:43
поделиться

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

НО: Все общие компиляторы (т.е. те, о которых я знаю) используют VTabels.
Затем Да. Любой класс, который имеет виртуальный метод или получен из класса (прямо или косвенно), который имеет виртуальный метод, будет иметь объекты с указателем на VTable.

Все другие вопросы, которые Вы задаете, будут зависеть от компилятора/аппаратных средств нет никакого реального ответа на те вопросы.

2
ответ дан 2 December 2019 в 03:43
поделиться

Отвечать на вопрос, о котором объекты (экземпляры с этого времени) имеют vtables и где, полезно думать о том, когда Вам нужен vtable указатель.

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

class A { virtual void f(); int a; };
class B: public A { virtual void f(); virtual void g(); int b; };
class C: public B { virtual void f(); virtual void g(); virtual void h(); int c; };
class D: public A { virtual void f(); int d; };
class E: public B { virtual void f(); int e; };

В результате Вам нужны пять vtables: A, B, C, D, и E вся потребность их собственный vtables.

Затем, необходимо знать что vtable использовать данный подсказка или ссылка на конкретный класс. Например, данные подсказка к A, необходимо знать достаточно о расположении таким образом, что можно получить vtable, которое говорит Вам, куда диспетчеризировать A:: f (). Данный подсказка к B, необходимо знать достаточно о расположении B для диспетчеризации B:: f () и B:: g (). И так далее и так далее.

Одна возможная реализация могла поместить vtable указатель как первого члена любого класса. Это означало бы, что расположение экземпляра A будет:

A's vtable;
int a;

И экземпляр B был бы:

A's vtable;
int a;
B's vtable;
int b;

И Вы могли сгенерировать корректный виртуальный код диспетчеризации от этого расположения.

Можно также оптимизировать расположение путем объединения vtable указателей vtables, которые имеют то же расположение или если Вы - подмножество другого. Таким образом в вышеупомянутом примере, Вы могли также расположение B как:

B's vtable;
int a;
int b;

Поскольку B vtable является надмножеством A. B vtable имеет записи для A:: f и B:: g, и A vtable имеет записи для A:: f.

Для полноты это - то, как Вы были бы расположение весь vtables, который мы видели до сих пор:

A's vtable: A::f
B's vtable: A::f, B::g
C's vtable: A::f, B::g, C::h
D's vtable: A::f
E's vtable: A::f, B::g

И фактические записи были бы:

A's vtable: A::f
B's vtable: B::f, B::g
C's vtable: C::f, C::g, C::h
D's vtable: D::f
E's vtable: E::f, B::g

Для множественного наследования Вы делаете тот же анализ:

class A { virtual void f(); int a; };
class B { virtual void g(); int b; };
class C: public A, public B { virtual void f(); virtual void g(); int c; };

И результирующие разметки были бы:

A: 
A's vtable;
int a;

B:
B's vtable;
int b;

C:
C's A vtable;
int a;
C's B vtable;
int b;
int c;

Вам нужны указатель на vtable совместимое с A и указатель на vtable совместимое с B, потому что ссылка на C может быть преобразована в ссылку A или B, и необходимо отправить виртуальные функции C.

От этого Вы видите, что количество vtable указателей, которые имеет конкретный класс, является, по крайней мере, количеством корневых классов, которые это получает из (или непосредственно или из-за суперкласса). Корневой класс является классом, который имеет vtable, которое не наследовалось классу, который также имеет vtable.

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

2
ответ дан 2 December 2019 в 03:43
поделиться

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

1
ответ дан 2 December 2019 в 03:43
поделиться

Каждый объект полиморфного типа будет иметь указатель на Vtable.

Где сохраненный VTable является иждивенцем на компиляторе.

0
ответ дан 2 December 2019 в 03:43
поделиться

Не обязательно

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

Новые компиляторы, которые анализируют код достаточно, могут устранять v-таблицы в некоторых случаях все же.

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

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

Так, в случаях как это не необходима v-таблица, и объекты могли бы закончиться не, имеют тот.

0
ответ дан 2 December 2019 в 03:43
поделиться
Другие вопросы по тегам:

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