tgoop.com/cxx95/88
Last Update:
#compiler
Теория девиртуализации
Виртуальные функции обычно работают через vtable. Если метод виртуальный, то вместо вызова точно известного метода, в рантайме вычисляется адрес метода, который зависит от динамического типа объекта.
Однако в некоторых случаях компилятор может "доказать", что он точно "знает" метод, который надо вызвать, несмотря на то, что метод виртуальный
Два простых примера: класс без final (нет оптимизации), класс с final (есть оптимизация - девиртуализация). Девиртуализованный вариант меньше дергает память.void CallDo(TDerived& obj) {
Компилятор считает, что можно девиртуализовать вызов в таких случаях:
obj.Do(); // будет ли девиртуализация?
}final
.
Смысл в том, что даже в случае работы с объектами TDerived*
/TDerived&
(которые могут указывать на наследника TDerived
) нужный метод будет одним и тем же, как его определил класс TDerived
.struct TDerived : IBase {
void Do() final override; // слово `final` тут
};struct TDerived final : IBase { // слово final тут
Однако есть еще одно условие, когда класс считается финальным - если у него финальный деструктор
void Do() override;
};struct TDerived : IBase {
~TDerived() final = default; // слово final тут
void Do() override;
};TDerived derived;
derived.Do(); // это же TDerived, инфа 100%TDerived MakeDerived(); // просто функция
На этом всё! Эта оптимизация логичная и скучная, потому что никаких чудес ожидать не приходится. Смысл в том, чтобы доказать что
// ...
MakeDerived().Do(); // здесь будет девиртуализация
TDerived{}.Do(); // здесь тоже девиртуализацияTDerived
/TDerived&
/TDerived*
указывает именно на объект TDerived
, а не на какой-то его потомок.
В реальном мире девиртуализация отрабатывает нечасто, так как надо, чтобы совпали два редких покемона кейса, оба противоречат ООП:
(1) работа с TDerived*
вместо IBase*
;
(2) Класс TDerived
или нужный метод финальный (не помню когда в последний раз писал final
).
Если вы хотите почитать про девиртуализацию "с нуля" с картинками, то есть крутой лонгрид.
Девиртуализация в C++ не гарантирована. В большинстве своем правила выше работают, но не всегда. В каких-то случаях оптимизировать вызовы виртуальных методов запрещено.
Например, в Apple macOS
А в "обычных" окружениях vtable лежат в секциях наподобии .rodata
. Эта секция защищена на уровне операционной системы - программа обычно сразу падает при попытке сделать туда какую-нибудь запись в рантайме.