C ++ Virtual Pointer и его механизм - PullRequest
0 голосов
/ 28 марта 2020

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

Я понимаю, что в C ++ виртуальная функция реализуется через виртуальный указатель и виртуальную таблицу.

Однако я не уверен, как работает компилятор C ++ расшифровать, какой виртуальный указатель использовать во время выполнения?

В следующем простом случае:

class Base {
public:
    virtual foo() {}
}

class Derived: public Base {
public:
    foo() override {}
}

int main() {
  D* p_derived = new Derived();
  B* p_base = p_derived;

  p_derived->foo();
  p_base->foo();
}

Я понимаю, что p_derived->foo() будет искать Derived::vptr per se для Derived::vtable (именуемо ) затем Derived::foo() и p_base->foo() следует по тому же пути, что и Derived::vptr -> Derived::vtable -> Derived::foo(). Но как p_base->foo() находит Derived::vptr , хотя его тип c равен Base*? Что мешает p_base вместо Base::vptr найти?

Большое спасибо

1 Ответ

2 голосов
/ 28 марта 2020

Я действительно думаю, что в связанном ответе есть все, что вам нужно знать, но там может быть немного не хватает.

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

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

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

Вещи могут быть чрезвычайно сложными с множественным наследованием, множественным виртуальным наследованием и т. Д. c. Могут даже быть вложенные таблицы: таблицы, указывающие на другие таблицы!

Тогда, конечно, мы получаем оптимизацию всей программы. LTO на Unixes. LTCG в MSV C, et c. Оптимизатор может go через программу, и если он может определить, что виртуальный вызов может только go для одной целевой функции, то он заменяет вызов не виртуальным прямым вызовом. Затем перезапускает проходной. Оптимизация по профилю может даже взять переменную виртуальную функцию и определить, что она вызывает класс A в 80% случаев. Тогда он может всегда вызывать A с проверкой вне строки, чтобы увидеть, действительно ли это B или что-то еще.

Но в простом случае, который вы выложили, у класса Base есть vtable с одним указателем на функцию чтобы foo. Когда запускается конструктор Base (), ему присваивается значение Base :: foo. После запуска Derived () ему присваивается значение Derived :: foo ()

Без сложного виртуального наследования или множественного наследования базовый класс (Base в вашем случае) всегда находится перед структурой. Таким образом, указатель на Base всегда указывает на фронт. Где находится vtable. Указатели на производные также указывают на фронт. Оба класса используют одну и ту же vtable и вызывают ту функцию, которая установлена ​​там.

...