Виртуальная функция
Эта статья нуждается в дополнительных цитатах для проверки . ( март 2013 г. ) |
Полиморфизм |
---|
Специальный полиморфизм |
Параметрический полиморфизм |
Подтипирование |
В объектно-ориентированном программировании, таком как часто используемое в C++ и Object Pascal , виртуальная функция или виртуальный метод — это наследуемая и переопределяемая функция или метод , которые отправляются динамически . Виртуальные функции являются важной частью полиморфизма (время выполнения) в объектно-ориентированном программировании (ООП). Они позволяют выполнять целевые функции, которые не были точно определены во время компиляции.
Большинство языков программирования, таких как JavaScript , PHP и Python , по умолчанию рассматривают все методы как виртуальные. [1] [2] и не предоставляйте модификатор для изменения этого поведения. Однако некоторые языки предоставляют модификаторы, предотвращающие переопределение методов производными классами (например, ключевые слова Final и Private в Java). [3] и PHP [4] ).
Цель
[ редактировать ]Концепция виртуальной функции решает следующую задачу:
В объектно-ориентированном программировании , когда производный класс наследуется от базового класса, на объект производного класса можно ссылаться через указатель или ссылку типа базового класса вместо типа производного класса. Если существуют методы базового класса, переопределенные производным классом, метод, фактически вызываемый такой ссылкой или указателем, может быть привязан (связан) либо «рано» (компилятором), в соответствии с объявленным типом указателя или ссылки, либо «поздно» (т. е. системой времени выполнения языка) в соответствии с фактическим типом объекта.
Виртуальные функции разрешаются «поздно». Если рассматриваемая функция является «виртуальной» в базовом классе, реализация функции в наиболее производном классе вызывается в соответствии с фактическим типом объекта, на который делается ссылка, независимо от объявленного типа указателя или ссылки. Если он не «виртуальный», метод разрешается «рано» и выбирается в соответствии с объявленным типом указателя или ссылки.
Виртуальные функции позволяют программе вызывать методы, которые даже не обязательно существуют на момент компиляции кода. [ нужна ссылка ]
В C++ виртуальные методы объявляются путем добавления virtual
ключевое слово в объявлении функции в базовом классе. Этот модификатор наследуется всеми реализациями этого метода в производных классах, а это означает, что они могут продолжать переопределять друг друга и выполняться с опозданием. И даже если методы, принадлежащие базовому классу, вызывают виртуальный метод, вместо этого они будут вызывать производный метод. Перегрузка возникает, когда два или более метода в одном классе имеют одинаковое имя метода, но разные параметры. Переопределение означает наличие двух методов с одинаковым именем и параметрами. Перегрузку также называют сопоставлением функций, а переопределением — динамическим сопоставлением функций.
Пример
[ редактировать ]С++
[ редактировать ]Например, базовый класс Animal
может иметь виртуальную функцию Eat
. Подкласс Llama
реализовал бы Eat
иначе, чем подкласс Wolf
, но можно вызвать Eat
в любом экземпляре класса, называемом Animal, и получите Eat
поведение конкретного подкласса.
class Animal {
public:
// Intentionally not virtual:
void Move() {
std::cout << "This animal moves in some way" << std::endl;
}
virtual void Eat() = 0;
};
// The class "Animal" may possess a definition for Eat if desired.
class Llama : public Animal {
public:
// The non virtual function Move is inherited but not overridden.
void Eat() override {
std::cout << "Llamas eat grass!" << std::endl;
}
};
Это позволяет программисту обрабатывать список объектов класса Animal
, приказывая каждому по очереди поесть (вызвав Eat
), без необходимости знать, какие животные могут быть в списке, как каждое животное ест или каким может быть полный набор возможных типов животных.
В C механизм виртуальных функций может быть реализован следующим образом:
#include <stdio.h>
/* an object points to its class... */
struct Animal {
const struct AnimalVTable *vtable;
};
/* which contains the virtual function Animal.Eat */
struct AnimalVTable {
void (*Eat)(struct Animal *self); // 'virtual' function
};
/*
Since Animal.Move is not a virtual function
it is not in the structure above.
*/
void Move(const struct Animal *self) {
printf("<Animal at %p> moved in some way\n", ( void* )(self));
}
/*
unlike Move, which executes Animal.Move directly,
Eat cannot know which function (if any) to call at compile time.
Animal.Eat can only be resolved at run time when Eat is called.
*/
void Eat(struct Animal *self) {
const struct AnimalVTable *vtable = *( const void** )(self);
if (vtable->Eat != NULL) {
(*vtable->Eat)(self); // execute Animal.Eat
} else {
fprintf(stderr, "'Eat' virtual method not implemented\n");
}
}
/*
implementation of Llama.Eat this is the target function
to be called by 'void Eat(struct Animal *self).'
*/
static void _Llama_eat(struct Animal *self) {
printf("<Llama at %p> Llama's eat grass!\n", ( void* )(self));
}
/* initialize class */
const struct AnimalVTable Animal = { NULL }; // base class does not implement Animal.Eat
const struct AnimalVTable Llama = { _Llama_eat }; // but the derived class does
int main(void) {
/* init objects as instance of its class */
struct Animal animal = { &Animal };
struct Animal llama = { &Llama };
Move(&animal); // Animal.Move
Move(&llama); // Llama.Move
Eat(&animal); // cannot resolve Animal.Eat so print "Not Implemented" to stderr
Eat(&llama); // resolves Llama.Eat and executes
}
Абстрактные классы и чисто виртуальные функции
[ редактировать ]или Чисто виртуальная функция чисто виртуальный метод — это виртуальная функция, которая должна быть реализована производным классом, если производный класс не является абстрактным . Классы, содержащие чисто виртуальные методы, называются «абстрактными», и их экземпляры не могут быть созданы напрямую. Подкласс может быть создан абстрактного класса напрямую только в том случае, если все унаследованные чисто виртуальные методы были реализованы этим классом или родительским классом. Чистые виртуальные методы обычно имеют объявление ( подпись ) и не имеют определения ( реализация ).
Например, абстрактный базовый класс MathSymbol
может предоставить чисто виртуальную функцию doOperation()
и производные классы Plus
и Minus
осуществлять doOperation()
предоставить конкретные реализации. Реализация doOperation()
не имело бы смысла в MathSymbol
класс, как MathSymbol
— абстрактное понятие, поведение которого определяется исключительно для каждого данного вида (подкласса) MathSymbol
. Аналогично, данный подкласс MathSymbol
не будет полным без реализации
doOperation()
.
Хотя чисто виртуальные методы обычно не имеют реализации в классе, который их объявляет, чисто виртуальные методы в некоторых языках (например, C++ и Python) могут содержать реализацию в объявляющем классе, обеспечивая запасное поведение или поведение по умолчанию, которому производный класс может делегировать. , если это уместно. [5] [6]
Чистые виртуальные функции также могут использоваться там, где объявления методов используются для определения интерфейса - аналогично тому, что явно указывает ключевое слово интерфейса в Java. При таком использовании производные классы будут предоставлять все реализации. В таком шаблоне проектирования абстрактный класс, служащий интерфейсом, будет содержать только чистые виртуальные функции, но не элементы данных или обычные методы. В C++ использование таких чисто абстрактных классов в качестве интерфейсов работает, поскольку C++ поддерживает множественное наследование . Однако, поскольку многие языки ООП не поддерживают множественное наследование, они часто предоставляют отдельный механизм интерфейса. Примером является язык программирования Java .
Поведение во время строительства и разрушения
[ редактировать ]Языки различаются своим поведением во время работы конструктора или деструктора объекта. По этой причине вызов виртуальных функций в конструкторах обычно не рекомендуется.
В C++ вызывается «базовая» функция. В частности, вызывается наиболее производная функция, которая не является более производной, чем текущий класс конструктора или деструктора. [7] : §15.7.3 [8] [9] Если эта функция является чисто виртуальной функцией, возникает неопределенное поведение . [7] : §13.4.6 [8] Это верно, даже если класс содержит реализацию этой чисто виртуальной функции, поскольку вызов чисто виртуальной функции должен быть явно определен. [10] Соответствующая реализация C++ не требуется (и, как правило, не способна) обнаруживать косвенные вызовы чисто виртуальных функций во время компиляции или компоновки . Некоторые системы времени выполнения выдают ошибку вызова чисто виртуальной функции при вызове чисто виртуальной функции во время выполнения .
В Java и C# вызывается производная реализация, но некоторые поля еще не инициализируются производным конструктором (хотя они инициализируются нулевыми значениями по умолчанию). [11] Некоторые шаблоны проектирования , такие как паттерн «Абстрактная фабрика» , активно продвигают такое использование в языках, поддерживающих эту возможность.
Виртуальные разрушители
[ редактировать ]Объектно-ориентированные языки обычно управляют выделением и освобождением памяти автоматически при создании и уничтожении объектов. Однако некоторые объектно-ориентированные языки позволяют при желании реализовать собственный метод деструктора. Если рассматриваемый язык использует автоматическое управление памятью, вызываемый пользовательский деструктор (обычно называемый в этом контексте финализатором) обязательно будет подходящим для рассматриваемого объекта. Например, если создается объект типа Wolf, наследующий Animal, и оба имеют собственные деструкторы, вызванный будет тот, который объявлен в Wolf.
В контексте ручного управления памятью ситуация может быть более сложной, особенно в отношении статической диспетчеризации . Если создается объект типа Wolf, но на него указывает указатель Animal, и именно этот тип указателя Animal удаляется, вызванный деструктор может фактически быть тем, который определен для Animal, а не деструктором для Wolf, если только деструктор не является виртуальным. . Это особенно актуально для C++, где такое поведение является частым источником ошибок программирования, если деструкторы не являются виртуальными.
См. также
[ редактировать ]- Абстрактный метод
- Наследование (объектно-ориентированное программирование)
- Суперкласс (информатика)
- Виртуальное наследование
- Виртуальный класс
- Интерфейс (объектно-ориентированное программирование)
- Объектная модель компонента
- Таблица виртуальных методов
Ссылки
[ редактировать ]- ^ «Полиморфизм (Учебные пособия по Java™ > Изучение языка Java > Интерфейсы и наследование)» . docs.oracle.com . Проверено 11 июля 2020 г.
- ^ «9. Классы — документация Python 3.9.2» . docs.python.org . Проверено 23 февраля 2021 г.
- ^ «Написание окончательных классов и методов (Учебные пособия по Java™ > Изучение языка Java > Интерфейсы и наследование)» . docs.oracle.com . Проверено 11 июля 2020 г.
- ^ «PHP: последнее ключевое слово — руководство» . www.php.net . Проверено 11 июля 2020 г.
- ^ Чистые виртуальные деструкторы - cppreference.com
- ^ "abc — Абстрактные базовые классы: @abc.abstractmethod"
- ^ Jump up to: а б «N4659: Рабочий проект стандарта языка программирования C++» (PDF) .
- ^ Jump up to: а б Чен, Раймонд (28 апреля 2004 г.). «Что такое __purecall?» .
- ^ Мейерс, Скотт (6 июня 2005 г.). «Никогда не вызывайте виртуальные функции во время создания или разрушения» .
- ^ Чен, Раймонд (11 октября 2013 г.). «Угловой случай C++: в базовом классе можно реализовать чисто виртуальные функции» .
- ^ Ганеш, С.Г. (1 августа 2011 г.). «Радость программирования: вызов виртуальных функций из конструкторов» .