В объектно-ориентированном программировании на таких языках, как C ++ и Object Pascal , виртуальная функция или виртуальный метод является наследуемой и переопределяемой функцией или методом, для которых упрощается динамическая диспетчеризация . Эта концепция является важной частью (выполнения) полиморфизма части объектно-ориентированного программирования (ООП). Короче говоря, виртуальная функция определяет целевую функцию, которая должна быть выполнена, но цель может быть неизвестна во время компиляции.
Большинство языков программирования, таких как Java , PHP и Python , по умолчанию рассматривают все методы как виртуальные [1] [2] и не предоставляют модификатор для изменения этого поведения. Однако некоторые языки предоставляют модификаторы для предотвращения переопределения методов производными классами (например, ключевое слово final в Java [3] и PHP [4] ).
Цель
Концепция виртуальной функции решает следующую проблему:
В объектно-ориентированном программировании, когда производный класс наследуется от базового класса, на объект производного класса можно ссылаться через указатель или ссылку на тип базового класса вместо типа производного класса. Если есть методы базового класса, переопределенные производным классом, метод, фактически вызываемый такой ссылкой или указателем, может быть привязан либо «рано» (компилятором), в соответствии с объявленным типом указателя или ссылки, либо «поздно» (т. е. исполняющей системой языка) в соответствии с фактическим типом объекта, на который имеется ссылка.
Виртуальные функции разрешаются «поздно». Если рассматриваемая функция является «виртуальной» в базовом классе, реализация функции наиболее производного класса вызывается в соответствии с фактическим типом упомянутого объекта, независимо от объявленного типа указателя или ссылки. Если это не «виртуальный», метод разрешается «раньше», а вызываемая функция выбирается в соответствии с объявленным типом указателя или ссылки.
Виртуальные функции позволяют программе вызывать методы, которые не обязательно даже существуют в момент компиляции кода.
В C ++ виртуальные методы объявляются путем добавления virtual
ключевого слова к объявлению функции в базовом классе. Этот модификатор наследуется всеми реализациями этого метода в производных классах, что означает, что они могут продолжать переопределять друг друга и иметь позднее связывание. И даже если методы, принадлежащие базовому классу, вызывают виртуальный метод, они вместо этого будут вызывать производный метод. Перегрузка возникает, когда два или более методов в одном классе имеют одно и то же имя метода, но разные параметры. Переопределение означает наличие двух методов с одинаковым именем и параметрами. Перегрузка также называется сопоставлением функций, а переопределение - отображением динамических функций.
Пример
Например, базовый класс Animal
может иметь виртуальную функцию Eat
. Подкласс Llama
будет реализован Eat
иначе, чем подкласс Wolf
, но можно вызвать Eat
любой экземпляр класса, называемый Animal, и получить Eat
поведение конкретного подкласса.
class Animal { public : // Умышленно не виртуальный: void Move ( void ) { std :: cout << "Это животное каким-то образом двигается" << std :: endl ; } virtual void Eat ( void ) = 0 ; };// При желании класс Animal может содержать определение Eat. class Llama : public Animal { public : // Невиртуальная функция Move наследуется, но не переопределяется. void Eat ( void ) override { std :: cout << "Ламы едят траву!" << std :: endl ; } };
Это позволяет программисту обрабатывать список объектов класса Animal
, сообщая каждому по очереди есть (путем вызова Eat
), без необходимости знать, какое животное может быть в списке, как каждое животное ест или какой полный набор возможных типы животных могут быть.
Мы можем лучше увидеть, как работают виртуальные функции, реализовав приведенный выше пример на C
#include / * объект указывает на свой класс ... * / struct Animal { const struct AnimalClass * class ; };/ * который содержит виртуальную функцию Animal.Eat * / struct AnimalClass { void ( * Eat ) ( struct Animal * ); // «виртуальная» функция };/ * Поскольку Animal.Move не является виртуальной функцией, ее нет в приведенной выше структуре. * / void Move ( struct Animal * self ) { printf ( "<Животное в% p> каким-то образом переместилось \ 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 ( "<Лама в% p> Лама ест траву! \ 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 и выполняет }
Абстрактные классы и чистые виртуальные функции
Чисто виртуальная функция или чисто виртуальный метод представляет собой виртуальную функцию , которая требуется , чтобы быть реализованы с помощью производного класса , если производный класс не является абстрактным . Классы, содержащие чистые виртуальные методы, называются «абстрактными», и они не могут быть созданы напрямую. Подкласс абстрактного класса может быть реализован только непосредственно , если все унаследовали чисто виртуальные методы были реализованы этим классом или родительского классом. Чистые виртуальные методы обычно имеют декларацию ( подпись ) и не имеют определения ( реализации ).
Например, абстрактный базовый класс MathSymbol
может предоставлять чистую виртуальную функцию doOperation()
, а также производные классы Plus
и Minus
реализацию doOperation()
для предоставления конкретных реализаций. Реализация doOperation()
не имела бы смысла в MathSymbol
классе, поскольку MathSymbol
это абстрактная концепция, поведение которой определяется исключительно для каждого данного вида (подкласса) MathSymbol
. Точно так же данный подкласс MathSymbol
не был бы полным без реализации doOperation()
.
Хотя чистые виртуальные методы обычно не имеют реализации в классе, который их объявляет, чистым виртуальным методам на некоторых языках (например, C ++ и Python) разрешено содержать реализацию в своем объявляющем классе, обеспечивая резервное поведение или поведение по умолчанию, которому производный класс может делегировать , при необходимости. [5] [6]
Чистые виртуальные функции также могут использоваться там, где объявления методов используются для определения интерфейса - аналогично тому, что явно указывает ключевое слово interface в Java. При таком использовании производные классы будут предоставлять все реализации. В таком шаблоне проектирования абстрактный класс, который служит интерфейсом, будет содержать только чистые виртуальные функции, но не элементы данных или обычные методы. В C ++ использование таких чисто абстрактных классов в качестве интерфейсов работает, потому что C ++ поддерживает множественное наследование . Однако, поскольку многие языки ООП не поддерживают множественное наследование, они часто предоставляют отдельный механизм интерфейса. Примером может служить язык программирования Java .
Поведение во время строительства и разрушения
Языки различаются по своему поведению во время работы конструктора или деструктора объекта. По этой причине обычно не рекомендуется вызывать виртуальные функции в конструкторах.
В C ++ вызывается «базовая» функция. В частности, вызывается наиболее производная функция, которая не более производная, чем класс текущего конструктора. [7] Если эта функция является чистой виртуальной функцией, возникает неопределенное поведение . [8] Это верно, даже если класс содержит реализацию этой чистой виртуальной функции. Соответствующая реализация C ++ не требуется (и, как правило, не способна) обнаруживать косвенные вызовы чистых виртуальных функций во время компиляции или компоновки . Некоторые системы времени выполнения выдают ошибку вызова чистой виртуальной функции при обнаружении вызова чистой виртуальной функции во время выполнения .
В Java и C # вызывается производная реализация, но некоторые поля еще не инициализированы производным конструктором (хотя они инициализируются нулевыми значениями по умолчанию). [9] Некоторые шаблоны проектирования , такие как шаблон абстрактной фабрики , активно продвигают это использование в языках, поддерживающих эту возможность.
Виртуальные деструкторы
Объектно-ориентированные языки обычно автоматически управляют выделением и освобождением памяти при создании и уничтожении объектов. Однако некоторые объектно-ориентированные языки позволяют при желании реализовать собственный метод деструктора. Если в рассматриваемом языке используется автоматическое управление памятью, вызываемый настраиваемый деструктор (обычно называемый финализатором в этом контексте) обязательно будет подходящим для рассматриваемого объекта. Например, если создается объект типа 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"
- ^ Мейерс, Скотт (6 июня 2005 г.). «Никогда не вызывайте виртуальные функции во время строительства или разрушения» .
- ^ «N4659: Рабочий проект стандарта языка программирования C ++» (PDF) . §13.4.
- ^ Ганеш С.Г. (1 августа 2011 г.). «Радость программирования: вызов виртуальных функций из конструкторов» .