TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.35K subscribers
Post #839 4.71K
​​Inline виртуальные методы
#опытным

В догонку предыдущего поста. Если можно сделать обычные методы inline, то можно и виртуальные сделать. Что это изменит?

Ну то есть такая иерархия:

class Base {
public:
virtual int foo() = 0;

protected:
int x = 2;
};

class Derived : public Base {
public:
int foo() override {
x *= 2;
return x;
}
};


Получим ли мы какой-то профит от того, что виртуальные методы теперь inline?

Посмотрим на такой код:

void simple() {
Derived d;
std::cout << d.foo();
}


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

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

void vec_base(std::vector<std::unique_ptr<Base>>& my_vec) {
for (auto &p : my_vec) {
std::cout << p->foo();
}
}
// другая TU
std::vector<std::unique_ptr<Base>> my_vec;
my_vec.push_back(new Derived());
vec_base(my_vec);


Здесь все понятно. Компилятор реально не знает, какие типы находятся внутри my_vec в функции vec_base. Поэтому все, что он может сделать - это использовать указатель на таблицу виртуальных функций и пусть рантайме уже решается, какой конкретно метод вызвать.

Теперь посмотрим такой код:

void vec_derived(std::vector<std::unique_ptr<Derived>>& my_vec) {
for (auto &p : my_vec) {
std::cout << p->foo();
}
}
// другая TU
std::vector<std::unique_ptr<Derived>> my_vec;
my_vec.push_back(new Derived());
vec_base(my_vec);


И здесь тоже не инлайнится код! Хотя компилятор же видит, что Derived - это единственный наследник.

Но на деле это может быть не так. Единица трансляции с vec_derived может не знать о наследниках уже Derived класса, которые определены отдельно. Компилятор не может достоверно доказать, что у Derived нет наследников, поэтому и не может инлайнить код. Опять vtable и оверхэд.

И здесь в игру вступает ключевое слово final и девиртуализация вызовов, о которой мы говорили тут и тут. Пометив класс как final, мы можем ожидать, что компилятор поймет, что никаких наследников у него нет. Поэтому он может встраивать вызов метода foo для Derived класса в последнем примере.

Вот код на годболте с последними примерами и final девиртуализацией. Немного упростил его, чтобы легче было ассемблер читать.

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

Учитывая, что

1️⃣ виртуальные функции обычно довольно громоздкие(их код будет генерится во всех единицах трансляции, что раздувает бинарь)

2️⃣ операции над непосредственно типами наследниками не распространены

3️⃣ а для девиртуализации нужно танцевать с бубнами

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

Be useful. Stay cool.

#cppcore #OOP #design
  • ❤ 15
  • 👍 7
  • 🔥 6
  • ❤‍🔥 2
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 →