Виртуальный метод (виртуальная функция) — в объектно-ориентированном программировании метод (функция)класса, который может быть переопределён в классах-наследниках так, что конкретная реализация метода для вызова будет определяться во время исполнения. Таким образом, программисту необязательно знать точный тип объекта для работы с ним через виртуальные методы: достаточно лишь знать, что объект принадлежит классу или наследнику класса, в котором метод объявлен.
Виртуальные методы — один из важнейших приёмов реализации полиморфизма. Они позволяют создавать общий код, который может работать как с объектами базового класса, так и с объектами любого его класса-наследника. При этом базовый класс определяет способ работы с объектами и любые его наследники могут предоставлять конкретную реализацию этого способа. В некоторых языках программирования, например в Java, нет понятия виртуального метода, данное понятие следует применять лишь для языков, в которых методы родительского класса не могут быть переопределены по умолчанию, а только с помощью некоторых вспомогательных ключевых слов. В некоторых же (как, например, в Python), все методы — виртуальные.
Базовый класс может и не предоставлять реализации виртуального метода, а только декларировать его существование. Такие методы без реализации называются «чистыми виртуальными» (перевод англ. pure virtual) или абстрактными. Класс, содержащий хотя бы один такой метод, тоже будет абстрактным. Объект такого класса создать нельзя (в некоторых языках допускается, но вызов абстрактного метода приведёт к ошибке). Наследники абстрактного класса должны предоставить реализацию для всех его абстрактных методов, иначе они, в свою очередь, будут абстрактными классами.
Для каждого класса, имеющего хотя бы один виртуальный метод, создаётся таблица виртуальных методов. Каждый объект хранит указатель на таблицу своего класса. Для вызова виртуального метода используется такой механизм: из объекта берётся указатель на соответствующую таблицу виртуальных методов, а из неё, по фиксированному смещению, — указатель на реализацию метода, используемого для данного класса. При использовании множественного наследования ситуация несколько усложняется за счёт того, что таблица виртуальных методов становится нелинейной.
Пример виртуальной функции на C++
Пример на C++, иллюстрирующий отличие виртуальных функций от невиртуальных:
В этом примере класс Ancestor
определяет две функции, одну из них виртуальную, другую — нет. КлассDescendant
переопределяет обе функции. Однако, казалось бы одинаковое обращение к функциям даёт разные результаты. На выводе программа даст следующее:
То есть, для определения реализации виртуальной функции используется информация о типе объекта и вызывается «правильная» реализация, независимо от типа указателя. При вызове невиртуальной функции,компилятор руководствуется типом указателя или ссылки, поэтому вызываются две разные реализацииfunction2()
, несмотря на то, что используется один и тот же объект.
Следует отметить, что в С++ можно, при необходимости, указать конкретную реализацию виртуальной функции, фактически вызывая её невиртуально:
для нашего примера выведет Ancestor::function1(), игнорируя тип объекта.
// небольшой пример использования виртуальных функций
#include <iostream.h>
class Base {
public:
virtual void who() { // определение виртуальной функции
cout << *Base\n";
}
};
class first_d: public Base {
public:
void who() { // определение who() применительно к first_d
cout << "First derivation\n";
}
};
class seconded: public Base {
public:
void who() { // определение who() применительно к second_d
cout << "Second derivation\n*";
}
};
int main()
{
Base base_obj;
Base *p;
first_d first_obj;
second_d second_obj;
p = &base_obj;
p->who(); // доступ к who класса Base
p = &first_obj;
p->who(); // доступ к who класса first_d
p = &second_ob;
p->who(); // доступ к who класса second_d
return 0;
}
Программа выдаст следующий результат:
Base
First derivation
Second derivation