TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.35K subscribers
Post #254 3.41K
​Девиртуализация доступа к полям виртуально наследованных классов

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

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

То, что мы хотим сделать, называется девиртуализацией. Мы хотим выполнить оптимизацию, которая позволит получить прямой доступ к полям класса, минуя таблицу виртуальных смещений. Мы действительно можем это сделать — достаточно лишь понять суть проблемы: компилятору неизвестно, будет ли текущий класс наследован кем-то другим из других единиц компиляции. Новые наследники добавят какое-то количество байт под свои поля и смещение изменится (но в текущей единице компиляции нам это будет неизвестно). Получается, нам достаточно явно ограничить возможность наследования от конкретного класса!

Тут следует сделать оговорку, что стандарт C++ никак не регламентирует реализацию оптимизаций. Следовательно, это необходимо дополнительно проверять для вашего компилятора. И теперь вы будете знать, что именно! 😉 Мы проверяли на компиляторах gcc, llvm clang, icc/icx под x86-64.

Как вы уже догадались, запретить наследование можно с помощью идентификатора со специальным значением final:
struct Child final : P2, P1 {};

Этого достаточно, чтобы гарантировать отсутствие наследников класса Child в других единицах трансляции. Следовательно, при работе с данным наследником смещение может быть вычислено на этапе компиляции и использовано без обращения к виртуальной таблице:
auto *pointer = new Child();

// Direct cast Child* -> Base*
auto *base = static_cast<Base*>(pointer);

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

Я видоизменил пример из предыдущей статьи и получил вот такого монстра бенчмарка. Тут появился шаблонный класс inheritance_cast, который в зависимости от булевой константы вызывает либо одну, либо другую реализацию для приведения типа (мотивация выше). Это нужно исключительно для моего бенчмарка. Писать такие вспомогательные классы вам нет никакого смысла, ведь вы должны знать, с каким классом вы работаете.

Новые результаты демонстрируют, что теперь скорость доступа стала сопоставимой с невиртуально наследованным классом. Следовательно, можно сделать вывод, что девиртуализация доступа позволяет сократить лишние действия!

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

#cppcore #compiler
  • 👍 20
  • 🔥 7
  • ❤ 4
More from @grokaemcpp
  1. Oct 14, 2026​​Что внутри std::condition_variable? #опытным Более менее все знакомы с примитивом синхро…
  2. Oct 12, 2026​​WAT #новичкам Как вы думаете, результат выражения: bool math = -1 < 1U; будет правдой ил…
  3. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  4. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  5. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  6. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
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 →