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

Почему vptr не является статичным?

Каждый класс, содержащий одну или несколько виртуальных функций, имеет связанную с ним Vtable. Указатель void, называемый vptr, указывает на vtable. Каждый объект этого класса содержит тот vptr, который указывает на тот же Vtable. Тогда почему нет vptr static? Вместо того, чтобы связывать vptr с объектом, почему бы не связать его с классом?

enter image description here

4b9b3361

Ответ 1

Класс выполнения объекта является свойством самого объекта. Фактически, vptr представляет класс времени выполнения и поэтому не может быть static. Тем не менее, это указывает на то, что все экземпляры одного и того же класса выполнения могут быть разделены.

Ответ 2

Ваша диаграмма неверна. Нет ни одной таблицы vtable, для каждого полиморфного типа существует одна vtable. Vptr для A указывает на таблицу vtable для A, vptr для A1 указывает на таблицу vtable для A1 и т.д.

Дано:

class A {
public:
  virtual void foo();
  virtual void bar();
};
class A1 : public A {
  virtual void foo();
};
class A2 : public A {
  virtual void foo();
};
class A3 : public A {
  virtual void bar();
  virtual void baz();
};

В таблице vtable для A содержится { &A::foo, &A::bar }
Vtable для A1 содержит { &A1::foo, &A::bar }
Vtable для A2 содержит { &A2::foo, &A::bar }
Vtable для A3 содержит { &A::foo, &A3::bar, &A3::baz }

Поэтому, когда вы вызываете a.foo(), компилятор следует за объектом vptr, чтобы найти vtable, а затем вызывает первую функцию в таблице vtable.

Предположим, что компилятор использует вашу идею, и мы пишем:

A1 a1;
A2 a2;
A& a = (std::rand() % 2) ? a1 : a2;
a.foo();

Компилятор просматривает базовый класс A и находит vptr для класса A, который (согласно вашей идее) является свойством static типа A, не являющимся членом объекта, ссылка A обязана. Означает ли это, что vptr указывает на таблицу vtable для A или A1 или A2 или что-то еще? Если он указал на vtable для A1, это было бы неправильно в 50% случаев, когда A относится к A2 и наоборот.

Теперь предположим, что мы пишем:

A1 a1;
A2 a2;
A& a = a1;
A& aa = a2;
a.foo();
aa.foo();

A и aa являются ссылками на A, но им нужны два разных vptrs, один указывает на vtable для A1 и один указывает на vtable для A2. Если vptr является статическим членом A, как он может иметь сразу два значения? Единственный логичный, последовательный выбор состоит в том, что статический vptr A указывает на vtable для A.

Но это означает, что вызов a.foo() вызывает A::foo(), когда он должен вызывать A1::foo(), а вызов aa.foo() также вызывает A::foo(), когда он должен вызывать A2::foo().

Очевидно, что ваша идея не реализует требуемую семантику, доказывая, что компилятор, использующий вашу идею, не может быть компилятором С++. Компилятор не может получить vtable для A1 из A, не зная, что такое производный тип (что вообще невозможно, ссылка на базу могла быть возвращена из функции, определенной в другая библиотека и может ссылаться на производный тип, который еще не был написан!) или путем хранения vptr непосредственно в объекте.

vptr должен быть другим для A1 и A2 и должен быть доступен, не зная динамического типа при доступе к ним через проводник или ссылку на базу, чтобы при получении vptr через ссылку на базу class, A, он по-прежнему указывает на правую таблицу vtable, а не на базовый класс vtable. Самый очевидный способ сделать это - сохранить vptr непосредственно в объекте. Альтернативным, более сложным решением будет сохранение карты адресов объектов в vptrs, например. что-то вроде std::map<void*, vtable*>, и найдите vtable для A, просмотрев &a, но он по-прежнему хранит один vptr для каждого объекта не по одному для каждого типа и потребует гораздо больше работы (и динамического выделения) для обновления карты каждый раз, когда полиморфные объекты создаются и уничтожаются и увеличивают общее использование памяти, потому что структура карты занимает пространство. Проще просто вставить vptr в сами объекты.

Ответ 3

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

Если вы думаете каким-то образом использовать typeid() внутренне, чтобы идентифицировать динамический тип, а затем вызвать с ним статический указатель, имейте в виду, что typeid() возвращает только динамический тип для объектов, принадлежащих к типам с виртуальными функциями; иначе он просто возвращает статический тип (§ 5.2.8 в текущем стандарте С++). Да, это означает, что он работает наоборот: typeid() обычно использует виртуальный указатель для идентификации динамического типа.

Ответ 4

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

class Sub : Parent {};

и значение типа Parent*, во время выполнения я не знаю, действительно ли это объект типа Parent или один из типов Sub. Vptr позволяет мне понять это.

Ответ 5

Как все утверждают, Vptr является свойством объекта. Давайте посмотрим, почему?

Предположим, что у нас есть три объекта Class Base{ virtual ~Base(); //Class Definition }; Class Derived: public Base{ //Class Definition }; Class Client: public Derived{ //Class Definition };

отношение холдинга Base < --- Производное < ---- Клиент. Класс клиента получен из производного класса, который, в свою очередь, получен из базы

Base * Ob = new Base; Derived * Od = new Derived; Client* Oc = new Client;

Всякий раз, когда Oc разрушается, он должен разрушать базовую часть, производную часть, а затем клиентскую часть данных. Чтобы помочь в этой последовательности, базовый деструктор должен быть виртуальным, а объект Oc destructor указывает на деструктор клиента. Когда объект Oc base destructor является виртуальным компилятором, добавляет код деструктору объекта Oc, чтобы вызвать производный деструктор и производный деструктор для вызова базового деструктора. Эта цепочка видит, что вся база данных, производные и клиентские данные разрушаются при уничтожении объекта Client.

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

Ответ 6

таблица виртуальных методов - для каждого класса. Объект содержит указатель на тип времени выполнения vptr.

Я не думаю, что это требование в стандартном бюсте, все компиляции, с которыми я работал, делают это так.

Это верно даже в вашем примере.