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

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

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

Цель

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

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

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

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

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

пример

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

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

класс Животное { общественный:  // Умышленно не виртуальный:  пустота Шаг(пустота) {    стандартное::cout << "Это животное каким-то образом движется" << стандартное::конец;  }  виртуальный пустота Есть(пустота) = 0;};// При желании класс Animal может содержать определение Eat.класс Лама : общественный Животное { общественный:  // Невиртуальная функция Move наследуется, но не отменяется.  пустота Есть(пустота) отменять {    стандартное::cout << "Ламы едят траву!" << стандартное::конец;  }};

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

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

#включают <stdio.h>/ * объект указывает на свой класс ... * /структура Животное {    const структура AnimalClass * класс;};/ * который содержит виртуальную функцию Animal.Eat * /структура AnimalClass {    пустота (*Есть)(структура Животное *); // виртуальная функция };/ * Поскольку Animal.Move не является виртуальной функцией   его нет в приведенной выше структуре. * /пустота Шаг(структура Животное * я){    printf("<Животное в% p> каким-то образом переместилось п", (пустота *) я);}/ * в отличие от Move, который выполняет Animal.Move напрямую,   Eat не может знать, какую функцию (если есть) вызывать во время компиляции.   Animal.Eat можно разрешить только во время выполнения, когда вызывается Eat. * /пустота Есть(структура Животное * я){    const структура AnimalClass * класс = *(const пустота **) я;    если (класс->Есть)         класс->Есть(я); // выполняем Animal.Eat    еще        fprintf(stderr, «Ешьте не реализовано п");}/ * реализация Llama.Eat это целевая функция    вызывается функцией void Eat (struct Animal *). * /статический пустота _Llama_eat(структура Животное * я){    printf("<Лама at% p> Лама ест траву! п", (пустота *) я);    }/ * инициализировать класс * /const структура AnimalClass Животное = {(пустота *) 0}; // базовый класс не реализует Animal.Eatconst структура AnimalClass Лама = {_Llama_eat};  // но производный класс делаетint основной(пустота){   / * инициализируем объекты как экземпляр своего класса * /   структура Животное животное = {& Животное};   структура Животное лама = {& Лама};   Шаг(& животное); // Animal.Move   Шаг(& лама);  // Llama.Move   Есть(& животное);  // не может разрешить Animal.Eat, поэтому выведите "Not Implemented" в stderr   Есть(& лама);   // разрешает Llama.Eat и выполняет}

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

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

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

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

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

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

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

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

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

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

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

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

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

использованная литература

  1. ^ «Полиморфизм (Учебники Java ™> Изучение языка Java> Интерфейсы и наследование)». docs.oracle.com. Получено 2020-07-11.
  2. ^ «Написание заключительных классов и методов (Учебники Java ™> Изучение языка Java> Интерфейсы и наследование)». docs.oracle.com. Получено 2020-07-11.
  3. ^ "PHP: последнее ключевое слово - руководство". www.php.net. Получено 2020-07-11.
  4. ^ Чистые виртуальные деструкторы - cppreference.com
  5. ^ Мейерс, Скотт (6 июня 2005 г.). «Никогда не вызывайте виртуальные функции во время строительства или разрушения».
  6. ^ Ганеш, С.Г. (1 августа 2011 г.). «Радость программирования: вызов виртуальных функций из конструкторов».