Виртуальная функция

редактировать

В объектно-ориентированном программировании, в таких языках, как C ++ и Object Pascal, виртуальная функция или виртуальный метод является наследуемой и переопределяемой функцией или методом для которых динамическая отправка облегчается. Эта концепция является важной частью (времени выполнения) полиморфизма части объектно-ориентированного программирования (ООП). Короче говоря, виртуальная функция определяет целевую функцию, которая должна быть выполнена, но цель может быть неизвестна во время компиляции.

Большинство языков программирования, таких как Java, PHP и Python, по умолчанию обрабатывают все методы как виртуальные и не предоставляют модификатор для изменить это поведение. Однако некоторые из них предоставляют модификаторы, чтобы избежать переопределения методов производными классами (как последнее ключевое слово в Java и PHP ).

Содержание
  • 1 Цель
  • 2 Пример
  • 3 Абстрактные классы и чисто виртуальные функции
  • 4 Поведение во время построения и разрушения
  • 5 Виртуальные деструкторы
  • 6 См. Также
  • 7 Ссылки
Цель

Концепция виртуальной функции решает следующую проблему:

В объектно-ориентированном программировании, когда производный класс наследуется от базового класса, объект производного класса может на него можно ссылаться через указатель или ссылку типа базового класса вместо типа производного класса. Если есть методы базового класса, переопределенные производным классом, метод, фактически вызываемый такой ссылкой или указателем, может быть привязан либо «рано» (компилятором), в соответствии с объявленным типом указателя или ссылки, либо «поздно» (т. е. исполняющей системой языка) в соответствии с действительным типом объекта, на который имеется ссылка.

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

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

В C ++ виртуальные методы объявляются путем добавления ключевого слова virtualк объявлению функции в базовом классе. Этот модификатор наследуется всеми реализациями этого метода в производных классах, что означает, что они могут продолжать переопределять друг друга и иметь позднее связывание. И даже если методы, принадлежащие базовому классу, вызывают виртуальный метод, они вместо этого будут вызывать производный метод. Перегрузка возникает, когда два или более методов в одном классе имеют одно и то же имя метода, но разные параметры. Переопределение означает наличие двух методов с одним и тем же именем и параметрами. Перегрузка также называется сопоставлением функций, а переопределение - отображением динамических функций.

Пример
Схема классов животного

Например, базовый класс Animalможет иметь виртуальную функцию Eat. Подкласс Llamaбудет реализовывать Eatиначе, чем подкласс Wolf, но можно вызвать Eatдля любого экземпляра класса, называемого Animal, и получить поведение Eatконкретного подкласса.

class Animal {public: // намеренно не виртуальный: void Move (void) {std :: cout << "This animal moves in some way" << std::endl; } virtual void Eat(void) = 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(void) override { std::cout << "Llamas eat grass!" << std::endl; } };

Это позволяет программисту обрабатывать список объектов класса Animal, сообщая каждому в повернуться, чтобы поесть (позвонив Ешь), без необходимости знать, какое животное может быть в списке, как каждое животное ест или каков полный набор возможных типов животных.

Мы можем лучше увидеть, как работают виртуальные функции, реализовав приведенный выше пример в C

#include / * объект указывает на свой класс... * / struct Animal {const struct AnimalClass * class ; }; / * который содержит виртуальную функцию Animal.Eat * / struct AnimalClass {void (* Eat) (struct Animal *); // «виртуальная» функция}; / * Поскольку Animal.Move не является виртуальной функцией, его нет в приведенной выше структуре. * / void Move (struct Animal * self) {printf ("каким-то образом перемещен \ n", (void *) self); } / * в отличие от Move, который выполняет Animal.Move напрямую, Eat не может знать, какую функцию (если есть) вызывать во время компиляции. Animal.Eat можно разрешить только во время выполнения, когда вызывается Eat. * / void Eat (struct Animal * self) {const struct AnimalClass * class = * (const void **) self; if (class->Eat) class->Eat (self); // выполняем Animal.Eat else fprintf (stderr, "Есть не реализовано \ n"); } / * реализация Llama.Eat, это целевая функция, которая будет вызываться void Eat (struct Animal *). * / static void _Llama_eat (struct Animal * self) {printf ("Лама ест траву! \ n", (void *) self); } / * инициализируем класс * / const struct AnimalClass Animal = {(void *) 0}; // базовый класс не реализует Animal.Eat const struct AnimalClass Llama = {_Llama_eat}; // но производный класс выполняет int main (void) {/ * инициализирует объекты как экземпляр своего класса * / struct Animal animal = {Animal}; struct Animal llama = {Llama}; Перемещение (животное); // Animal.Move Move (llama); // Llama.Move Eat (animal); // не удается разрешить Animal.Eat, поэтому выведите «Не реализовано» в stderr Eat (llama); // разрешает Llama.Eat и выполняет}
Абстрактные классы и чистые виртуальные функции

A чистая виртуальная функция или чистый виртуальный метод - это виртуальная функция, которая должна быть реализована производным class, если производный класс не abstract. Классы, содержащие чистые виртуальные методы, называются «абстрактными», и они не могут быть созданы напрямую. Подкласс абстрактного класса может быть создан только напрямую, если все унаследованные чистые виртуальные методы были реализованы этим классом или родительским классом. Чистые виртуальные методы обычно имеют объявление (подпись ) и не имеют определения (реализация ).

В качестве примера абстрактный базовый класс MathSymbolможет предоставлять чистую виртуальную функцию doOperation ()и производные классы Plusи Минусреализовать doOperation ()для обеспечения конкретных реализаций. Реализация doOperation ()не имела бы смысла в классе MathSymbol, поскольку MathSymbolявляется абстрактной концепцией, поведение которой определяется исключительно для каждого данного вида (подкласса) MathSymbol. Точно так же данный подкласс MathSymbolне был бы полным без реализации doOperation ().

Хотя чистые виртуальные методы обычно не имеют реализации в классе, который их объявляет, чистые виртуальные методы в C ++ разрешено содержать реализацию в своем объявляющем классе, обеспечивая откат или поведение по умолчанию, которому производный класс может делегировать, если необходимо.

Чистые виртуальные функции также могут использоваться там, где объявления методов используются для определения interface - аналогично тому, что явно указывает ключевое слово interface в Java. При таком использовании производные классы будут предоставлять все реализации. В таком шаблоне проектирования абстрактный класс, который служит интерфейсом, будет содержать только чистые виртуальные функции, но не элементы данных или обычные методы. В C ++ использование таких чисто абстрактных классов в качестве интерфейсов работает, поскольку C ++ поддерживает множественное наследование. Однако, поскольку многие языки ООП не поддерживают множественное наследование, они часто предоставляют отдельный механизм интерфейса. Примером может служить язык программирования Java.

Поведение во время создания и разрушения

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

В C ++ вызывается «базовая» функция. В частности, вызывается наиболее производная функция, которая не более производная, чем класс текущего конструктора. Если эта функция является чистой функцией, возникает неопределенное поведение.

В Java и C # вызывается производная реализация, но некоторые поля еще не инициализированы производным конструктором (хотя они инициализируются нулевыми значениями по умолчанию). Некоторые шаблоны проектирования , такие как шаблон абстрактной фабрики, активно продвигают это использование в языках, поддерживающих эту возможность.

Виртуальные деструкторы

Объектно-ориентированные языки обычно автоматически управляют выделением и освобождением памяти при создании и уничтожении объектов. Однако некоторые объектно-ориентированные языки позволяют при желании реализовать собственный метод деструктора. Если в рассматриваемом языке используется автоматическое управление памятью, вызываемый настраиваемый деструктор (обычно называемый финализатором в этом контексте) обязательно будет подходящим для рассматриваемого объекта. Например, если создается объект типа Wolf, который наследует Animal, и оба имеют настраиваемые деструкторы, вызываемый будет объявлен в Wolf.

В контексте ручного управления памятью ситуация может быть более сложной, особенно в отношении статической диспетчеризации. Если объект типа Wolf создается, но на него указывает указатель Animal, и именно этот тип указателя Animal удаляется, вызываемый деструктор может фактически быть тем, который определен для Animal, а не для Wolf, если только деструктор не является виртуальным.. Это особенно верно в случае C ++, где поведение является частым источником ошибок программирования, если деструкторы не виртуальные.

См. Также
Ссылки
Последняя правка сделана 2021-06-18 03:36:34
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте