Middle
167
questionbank

Как работает механизм виртуальности в C++?

Получайте ответы на вопросы в реальном времени с Sobes Copilot
Ответ от нейросети sobes.tech

Механизм виртуальности в C++ реализуется с помощью указателей на функции (таблица виртуальных функций, или VMT - Virtual Method Table) и указателя на эту таблицу (vptr - virtual pointer), который добавляется в каждый объект класса с виртуальными функциями или унаследованный от такого класса.

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

В каждом объекте класса (или производного от него) добавляется скрытый указатель vptr, который ссылается на VMT соответствующего типа объекта.

Когда виртуальная функция вызывается через указатель или ссылку на базовый класс, компилятор генерирует код, который использует vptr объекта для поиска адреса нужной функции в VMT и ее вызова. Это называется динамической диспетчеризацией (или поздним связыванием), так как решение о том, какая именно функция будет вызвана, принимается во время выполнения программы, а не во время компиляции.

Пример:

cpp

В этом примере, несмотря на то что ptr имеет тип Base*, при вызове ptr->greet() фактически будет вызвана реализация функции greet из класса Derived, потому что vptr объекта Derived, на который указывает ptr, ссылается на VMT класса Derived.

Невртуальные функции вызываются через статическую диспетчеризацию (раннее связывание), где адрес функции определяется во время компиляции на основе типа указателя или ссылки.

Ключевые аспекты:

  • Полиморфизм времени выполнения: Виртуальные функции обеспечивают возможность вызывать правильную версию функции в зависимости от фактического типа объекта, а не от типа указателя/ссылки.
  • Накладные расходы: Виртуальные функции добавляют небольшие накладные расходы на создание объекта (для vptr) и каждый вызов (для поиска в VMT).
  • override: Ключевое слово override в C++11 (и более поздних) помогает предотвратить ошибки при переопределении виртуальных функций, указывая, что функция в производном классе должна переопределять функцию из базового класса.
  • Виртуальный деструктор: Важно объявлять деструктор базового класса виртуальным, если планируется удалять объекты производных классов через указатель на базовый класс, чтобы избежать утечек памяти.
cpp