stringtranslate.com

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

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

Большинство языков программирования, таких как JavaScript , PHP и Python , по умолчанию рассматривают все методы как виртуальные [1] [2] и не предоставляют модификаторов для изменения этого поведения. Однако некоторые языки предоставляют модификаторы, предотвращающие переопределение методов производными классами (например, ключевые слова Final и Private в Java [3] и PHP [4] ).

Цель

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

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

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

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

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

Пример

С++

Диаграмма классов животных

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

class Animal { public : // Намеренно не виртуальный: void Move () { std :: cout << "Это животное каким-то образом движется" << std :: endl ; } виртуальная пустота Eat () = 0 ; };                  // При желании класс Animal может содержать определение Eat. class Llama : public Animal { public : // Невиртуальная функция Move наследуется, но не переопределяется. void Eat () override { std :: cout << "Ламы едят траву!" << std :: endl ; } };                 

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

В C механизм виртуальных функций может быть реализован следующим образом:

#include <stdio.h> /* объект указывает на свой класс... */ struct Animal { const struct AnimalVTable * vtable ; };      /* который содержит виртуальную функцию Animal.Eat */ struct AnimalVTable { void ( * Eat )( struct Animal * self ); // "виртуальная" функция };       /*  Поскольку Animal.Move не является виртуальной функцией,  ее нет в приведенной выше структуре. */ void Move ( const struct Animal * self ) { printf ( "<Животное в %p> каким-то образом переместилось \n " , ( void * )( self )); }         /*  в отличие от Move, который выполняет Animal.Move напрямую,  Eat не может знать, какую функцию (если таковая имеется) вызывать во время компиляции.  Animal.Eat можно разрешить только во время выполнения, когда вызывается Eat. */ void Eat ( struct Animal * self ) { const struct AnimalVTable * vtable = * ( const void ** )( self ); if ( vtable -> Eat != NULL ) { ( * vtable -> Eat )( self ); // выполняем Animal.Eat } else { fprintf ( stderr , "Виртуальный метод 'Eat' не реализован \n " ); } }                           /*  реализация Llama.Eat — это целевая функция,  вызываемая 'void Eat(struct Animal *self).' */ static void _Llama_eat ( struct Animal * self ) { printf ( "<Ллама в %p> Лама ест траву! \n " , ( void * )( self )); }          /* инициализация класса */ const struct AnimalVTable Animal = { NULL }; // базовый класс не реализует Animal.Eat const struct AnimalVTable Llama = { _Llama_eat }; // но производный класс делает это                int main ( void ) { /* инициализируем объекты как экземпляры своего класса */ struct Animal Animal = { & Animal }; struct Animal llama = { & Llama }; Двигаться ( & животное ); // Animal.Move Move ( & лама ); // Llama.Move Eat ( & животное ); // невозможно разрешить Animal.Eat, поэтому выведите «Not Implemented» в stderr Eat ( & llama ); // разрешает Llama.Eat и выполняет }                         

Абстрактные классы и чисто виртуальные функции

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

Например, абстрактный базовый класс 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++, где такое поведение является частым источником ошибок программирования, если деструкторы не являются виртуальными.

Смотрите также

Рекомендации

  1. ^ «Полиморфизм (Учебные пособия по Java™> Изучение языка Java> Интерфейсы и наследование)» . docs.oracle.com . Проверено 11 июля 2020 г.
  2. ^ «9. Классы — документация Python 3.9.2» . docs.python.org . Проверено 23 февраля 2021 г.
  3. ^ «Написание окончательных классов и методов (Учебные пособия по Java™> Изучение языка Java> Интерфейсы и наследование)» . docs.oracle.com . Проверено 11 июля 2020 г.
  4. ^ «PHP: окончательное ключевое слово — Руководство» . www.php.net . Проверено 11 июля 2020 г.
  5. ^ Чистые виртуальные деструкторы - cppreference.com
  6. ^ "abc — Абстрактные базовые классы: @abc.abstractmethod"
  7. ^ ab «N4659: Рабочий проект стандарта языка программирования C++» (PDF) .
  8. ↑ Аб Чен, Раймонд (28 апреля 2004 г.). «Что такое __purecall?».
  9. Мейерс, Скотт (6 июня 2005 г.). «Никогда не вызывайте виртуальные функции во время строительства или разрушения».
  10. Чен, Раймонд (11 октября 2013 г.). «Угловой случай C++: в базовом классе можно реализовать чистые виртуальные функции».
  11. Ганеш, SG (1 августа 2011 г.). «Радость программирования: вызов виртуальных функций из конструкторов».