TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.36K subscribers
Post #292 3.73K
Девиртуализация вызовов. Ч1
#опытным

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

Вызов виртуальных методов требует дополнительного разыменования указателя для вызова нужной реализации метода. Эта дополнительная операция как раз и тратит ценные такты процессора. А что нам мешает от неё избавиться? По большому счёту, если мы уверены, что работаем с объектом конкретного типа, то мы могли бы сделать что-то такое:
// object is always `Derived`
void baz(Base &object)
{
// explicit cast to Derived
static_cast<Derived&>(object).Derived::vmethod();
}


Однако, такой код выглядит вычурно и получается совсем не гибким в перспективе дальнейшей разработки. Ну вдруг кто-то ещё наследуется от Derived да перегрузит метод? Значит надо вызывать другой - идём переписывать код. К счастью, в современных компиляторах есть возможность выполнить такой трюк более безопасным и элегантным способом.

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

Будем рассуждать логически, что необходимо гарантировать компилятору, чтобы применить такую оптимизацию?
П.1: Неизменность виртуальной таблицы методов
П.2: Однозначность типа полиморфного объекта
П.3: Неизменность виртуального указателя в полиморфном объекте
П.4: Однозначность кандидата при вызове виртуального метода

Компилятор генерирует виртуальные таблицы и не рассчитывает, что вы будете их подменять. Следовательно, П.1 будет выполняться автоматически. В П.2 компилятору необходимо иметь возможность отследить динамический тип полиморфного объекта: чем глубже это получится сделать, тем лучше можно оптимизировать код. Появление П.3 обусловлено тем, что указатель на виртуальную таблицу можно перезаписать, а это уже должно приводить к вызову других методов. Необходимость П.4 обусловлена тем, что в других единицах компиляции могут появиться другие наследники полиморфного класса, о которых мы ничего не знаем в рамках текущей единицы компиляции.

Тут следует сделать оговорку, что стандарт C++ никак не регламентирует реализацию оптимизаций. Следовательно, они могут не выполняться. Данный материал актуален для компиляторов gcc, llvm clang, icc/icx под x86-64:
- GCC (6.1 и выше,
см. подробнее)
- LLVM, Clang (8.0.0 и выше,
см. подробнее)
- Intel Compiler (icx 2021.1.2 и выше, icc 19.0.0 и выше)


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

Классика
Начнём с того, что компилятор сам пытается выполнять прямую подстановку вызова метода. Из живого примера:
struct Base
{
virtual void vmethod();
};

struct Derived : public Base
{
void vmethod() override;
};

int main()
{
Base *object = new Derived();

// call direct Derived::vmethod()
object->vmethod();
}

В данном случае, у компилятора получается проследить все операции над указателем object до вызова vmethod, чтобы доказать П.2 и П.3. Условия П.4 выполняется, т.к. main - это единственная точка входа в программу.

Запрет на переопределение
Идентификатор final, используемый как при объявлении классов, так и при объявлении методов, позволяет гарантировать, что других перегрузок методов не может быть в принципе:
struct Base
{
virtual void vmethod();
};

struct DerivedA : public Base
{
// Can't override `DerivedA::vmethod`
// in child classes
void vmethod() override final;
};

// Can't inherit from `DerivedB`
struct DerivedB final : public Base
{
void vmethod() override;
};

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

Продолжение в комментариях 👇

#optimization #hardcore #howitworks
  • 🔥 9
  • 👍 7
  • ❤‍🔥 2
  • ⚡ 2
  • ❤ 1
  • 🤯 1
More from @grokaemcpp
  1. Oct 12, 2026​​WAT #новичкам Как вы думаете, результат выражения: bool math = -1 < 1U; будет правдой ил…
  2. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  3. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  4. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  5. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
  6. Sep 28, 2026​​Stacktrace #опытным Одна из проблема исключений - непонятно, откуда оно прилетело. Ну да…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →