Перейти к содержанию

Последнее обновление:2026-10-13

Виртуальные функции в C++

Об авторе:

Личный публичный аккаунт в WeChat:

Виртуальные функции в C++ | C++那些事

1. Виртуальные функции и полиморфизм времени выполнения

Соответствующий код: emp.cpp

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

2. vptr и vtable

См. vptr и vtable

3. Аргументы по умолчанию в виртуальных функциях

Соответствующий код: default_arg.cpp

Аргументы по умолчанию связываются статически, а виртуальные функции — динамически. Какой аргумент по умолчанию будет использован, зависит от типа самого указателя или ссылки, а не от типа объекта.

4. Можно или нельзя

(1) Можно ли объявить статическую функцию виртуальной?

Причин в основном две:

Статическую функцию нельзя объявить виртуальной, а также её нельзя квалифицировать ключевыми словами const и volatile

Функция-член static не принадлежит ни одному объекту или экземпляру класса, поэтому добавление к ней virtual не имеет никакого смысла.

Виртуальные функции полагаются на vptr и vtable. vptr — это указатель, который создаётся в конструкторе класса, и доступ к нему возможен только через указатель this; у статических функций-членов нет указателя this, поэтому они не могут получить доступ к vptr.

Код для изучения: static_error.cpp

(2) Может ли конструктор быть виртуальным?

Конструктор нельзя объявить виртуальным. Кроме того, помимо inline и explicit, конструктору не разрешено использовать никакие другие ключевые слова.

Почему конструктор не может быть виртуальным?

Хотя таблица виртуальных функций vtable строится ещё на этапе компиляции, указатель на таблицу виртуальных функций vptr порождается только на этапе выполнения, при создании экземпляра объекта. Если класс содержит виртуальные функции, компилятор добавляет в конструктор код, создающий vptr. И вот проблема: если бы конструктор был виртуальным, ему понадобился бы vptr для доступа к vtable, но в этот момент vptr ещё не существует. Поэтому конструктор не может быть виртуальным.

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

Код для изучения:

(3) Может ли деструктор быть виртуальным?

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

Код для изучения:

(4) Может ли виртуальная функция быть закрытой (private)?

  • Когда указатель базового класса указывает на объект производного класса, вызывается функция объекта производного класса;
  • int main() должен быть объявлен дружественным классу Base, иначе компиляция завершится ошибкой. Компилятор сообщает: ptr не может получить доступ к закрытой функции. Конечно, если базовый класс объявлен public, а производный — private, проблема исчезает.

Код для изучения:

(5) Может ли виртуальная функция быть встроенной (inline)?

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

  • Виртуальная функция может быть встроенной функцией; inline может квалифицировать виртуальную функцию, но когда виртуальная функция проявляет полиморфизм, она не может быть встроенной.
  • Встраивание — это предложение компилятору, сделанное на этапе компиляции, тогда как полиморфизм виртуальных функций относится к времени выполнения; компилятор не может знать, какой код будет вызван во время выполнения, поэтому виртуальная функция не может быть встроенной, когда она ведёт себя полиморфно (во время выполнения).
  • Единственный случай, когда inline virtual может быть встроен, — когда компилятор знает, какому классу принадлежит вызываемый объект (например Base::who()); это происходит, только когда у компилятора есть сам объект, а не указатель или ссылка на него.

Код для изучения:

5. RTTI и dynamic_cast

RTTI (Run-Time Type Identification, информация о типах во время выполнения) позволяет программе с помощью указателя или ссылки на базовый класс проверить фактический тип производного класса объекта, на который они указывают.

В объектно-ориентированном программировании иногда нужно выяснить во время выполнения, можно ли использовать объект как некоторый полиморфный тип. Аналогично оператору instanceof в Java и операторам as и is в C#, C++ предоставляет dynamic_cast для динамического приведения типов. По сравнению с C-приведениями и reinterpret_cast из C++, dynamic_cast выполняет проверки безопасности типов и является преобразованием на основе запроса возможностей (Capability Query), поэтому для преобразований между полиморфными типами рекомендуется использовать именно dynamic_cast.

Код для изучения:

6. Чистые виртуальные функции и абстрактные классы

См. Чистые виртуальные функции и абстрактные классы

Похожие темы

Комментарии