Виртуальный метод

Виртуальный метод

Виртуальный метод (виртуальная функция) — в объектно-ориентированном программировании метод (функция) класса, который может быть переопределён в классах-наследниках так, что конкретная реализация метода для вызова будет определяться во время исполнения. Таким образом, программисту необязательно знать точный тип объекта для работы с ним через виртуальные методы: достаточно лишь знать, что объект принадлежит классу или наследнику класса, в котором метод объявлен.

Виртуальные методы — один из важнейших приёмов реализации полиморфизма. Они позволяют создавать общий код, который может работать как с объектами базового класса, так и с объектами любого его класса-наследника. При этом базовый класс определяет способ работы с объектами и любые его наследники могут предоставлять конкретную реализацию этого способа. В некоторых языках программирования, например в Java, нет понятия виртуального метода, данное понятие следует применять лишь для языков, в которых методы родительского класса не могут быть переопределены по умолчанию, а только с помощью некоторых вспомогательных ключевых слов. В некоторых же (как, например, в Python), все методы — виртуальные.

Базовый класс может и не предоставлять реализации виртуального метода, а только декларировать его существование. Такие методы без реализации называются «чистыми виртуальными» (перевод англ.  pure virtual) или абстрактными. Класс, содержащий хотя бы один такой метод, тоже будет абстрактным. Объект такого класса создать нельзя (в некоторых языках допускается, но вызов абстрактного метода приведёт к ошибке). Наследники абстрактного класса должны предоставить реализацию для всех его абстрактных методов, иначе они, в свою очередь, будут абстрактными классами.

Для каждого класса, имеющего хотя бы один виртуальный метод, создаётся таблица виртуальных методов. Каждый объект хранит указатель на таблицу своего класса. Для вызова виртуального метода используется такой механизм: из объекта берётся указатель на соответствующую таблицу виртуальных методов, а из неё, по фиксированному смещению, — указатель на реализацию метода, используемого для данного класса. При использовании множественного наследования или интерфейсов ситуация несколько усложняется за счёт того, что таблица виртуальных методов становится нелинейной.

Содержание

Пример виртуальной функции на C++

Пример на C++, иллюстрирующий отличие виртуальных функций от невиртуальных:

class Ancestor
{
public:
  virtual void  function1 () { cout << "Ancestor::function1()" << endl; }
  void          function2 () { cout << "Ancestor::function2()" << endl; }
};
 
class Descendant : public Ancestor
{
public:
  virtual void  function1 () { cout << "Descendant::function1()" << endl; }
  void          function2 () { cout << "Descendant::function2()" << endl; }
};
 
Descendant*  pointer      = new Descendant ();
Ancestor*    pointer_copy = pointer;
 
pointer->function1 ();
pointer->function2 ();
 
pointer_copy->function1 ();
pointer_copy->function2 ();

В этом примере класс Ancestor определяет две функции, одну из них виртуальную, другую — нет. Класс Descendant переопределяет обе функции. Однако, казалось бы одинаковое обращение к функциям даёт разные результаты. На выводе программа даст следующее:

    Descendant::function1()
    Descendant::function2()
    Descendant::function1()
    Ancestor::function2()

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

Следует отметить, что в С++ можно, при необходимости, указать конкретную реализацию виртуальной функции, фактически вызывая её невиртуально:

pointer->Ancestor::function1 ();

для нашего примера выведет Ancestor::function1(), игнорируя тип объекта.

Пример виртуальной функции в Delphi

Язык Object Pascal, использующийся в Delphi, тоже поддерживает полиморфизм. Рассмотрим пример:

Объявим два класса. Предка (Ancestor):

 TAncestor = class
 private
 protected
 public
   {Виртуальная процедура.} 
   procedure VirtualProcedure; virtual; 
   procedure StaticProcedure;
 end;

и его потомка (Descendant):

 TDescendant = class(TAncestor)
 private
 protected
 public
    {Перекрытие виртуальной процедуры.}
   procedure VirtualProcedure; override;
   procedure StaticProcedure;
 end;

Как видно в классе предке объявлена виртуальная функция — VirtualProcedure. Чтобы воспользоваться достоинствами полиморфизма, её нужно перекрыть в потомке.

Реализация выглядит следующим образом:

 { TAncestor }
   
 procedure TAncestor.StaticProcedure;
 begin
   ShowMessage('Ancestor static procedure.');
 end;
   
 procedure TAncestor.VirtualProcedure;
 begin
   ShowMessage('Ancestor virtual procedure.');
 end;
 { TDescendant }
   
 procedure TDescendant.StaticProcedure;
 begin
   ShowMessage('Descendant static procedure.');
 end;
   
 procedure TDescendant.VirtualProcedure;
 begin
   ShowMessage('Descendant override procedure.');
 end;

Посмотрим как это работает:

 procedure TForm2.BitBtn1Click(Sender: TObject);
 var
   MyObject1: TAncestor;
   MyObject2: TAncestor;
 begin
   MyObject1 := TAncestor.Create;
   MyObject2 := TDescendant.Create;
   try
     MyObject1.StaticProcedure;
     MyObject1.VirtualProcedure;
     MyObject2.StaticProcedure;
     MyObject2.VirtualProcedure;
   finally
     MyObject1.Free;
     MyObject2.Free;
   end;
 end;

Заметьте, что в разделе var мы объявили два объекта MyObject1 и MyObject2 типа TAncestor. А при создании MyObject1 создали как TAncestor, а MyObject2 как TDescendant. Вот что мы увидим при нажатии на кнопку BitBtn1:

  1. Ancestor static procedure.
  2. Ancestor virtual procedure.
  3. Ancestor static procedure.
  4. Descendant override procedure.

Для MyObject1 все понятно, просто вызвались указанные процедуры. А вот для MyObject2 это не так.

Вызов MyObject2.StaticProcedure; привел к появлению «Ancestor static procedure.». Ведь мы объявили MyObject2: TAncestor, поэтому и была вызвана процедура StaticProcedure; класса TAncestor.

А вот вызов MyObject2.VirtualProcedure; привел к вызову VirtualProcedure; реализованной в потомке(TDescendant). Это произошло потому, что MyObject2 был создан не как TAncestor, а как TDescendant: MyObject2 := TDescendant.Create; . И виртуальный метод VirtualProcdure был перекрыт.

В Delphi полиморфизм реализован с помощью так называемой виртуальной таблицы методов (или VMT).

Достаточно часто виртуальные методы забывают перекрыть с помощью ключевого слова override. Это приводит к закрытию метода. В этом случае замещения методов в VMT не произойдет и требуемая функциональность не будет получена.

Эта ошибка отслеживается компилятором, который выдаёт соответствующее предупреждение.

Вызов метода предка из перекрытого метода

Бывает необходимо вызвать метод предка в перекрытом методе.

Объявим два класса. Предка(Ancestor):

 TAncestor = class
 private
 protected
 public
   {Виртуальная процедура.} 
   procedure VirtualProcedure; virtual; 
 end;

и его потомка (Descendant):

 TDescendant = class(TAncestor)
 private
 protected
 public
    {Перекрытие виртуальной процедуры.}
   procedure VirtualProcedure; override;
 end;

Обращение к методу предка реализуется с помощью ключевого слова «inherited»

 procedure TDescendant.VirtualProcedure;
 begin
     inherited;
 end;

Стоит помнить, что в Delphi деструктор должен быть обязательно перекрытым — «override» — и содержать вызов деструктора предка

TDescendant = class(TAncestor)
 private
 protected
 public
    destructor Destroy; override;
 end;
 destructor TDescendant. Destroy;
 begin
     inherited;
 end;

В языке C++ не нужно вызывать конструктор и деструктор предка, деструктор должен быть виртуальным. Деструкторы предков вызовутся автоматически. Чтобы вызвать метод предка, нужно явно вызвать метод:

class Ancestor
{
public:
  virtual void  function1 () { printf("Ancestor::function1"); }
};
 
class Descendant : public Ancestor
{
public:
  virtual void  function1 () {
     printf("Descendant::function1");
     Ancestor::function1(); // здесь будет напечатано "Ancestor::function1"
  }
};

Для вызова конструктора предка нужно указать конструктор:

class Descendant : public Ancestor
{
public:
  Descendant(): Ancestor(){}
};


Еще один пример

class A
{
public:
     virtual int function () {
        return 1;
     }
 
     int get() {
         return this->function();
     }
};
 
class B: public A
{
public:
     int function() {
        return 2;
     }
};
 
#include <iostream>
 
int main() {
   A a;
   B b; 
 
   std::cout << b.get() << std::endl; // 2
 
   return 0;
}

Несмотря на то, что в классе B отсутствует метод get(), его можно позаимствовать у класса A, при этом результат работы этого метода вернет вычисления для B::function()!

См. также

Ссылки


Wikimedia Foundation. 2010.

Игры ⚽ Поможем сделать НИР

Полезное


Смотреть что такое "Виртуальный метод" в других словарях:

  • виртуальный метод доступа к памяти — — [http://www.iks media.ru/glossary/index.html?glossid=2400324] Тематики электросвязь, основные понятия EN virtual storage access methodVSAM …   Справочник технического переводчика

  • виртуальный телекоммуникационный метод доступа — Программа IBM, постоянно хранящаяся в памяти на сервере и управляющая обменом информацией и потоками данных в сети SNA. [http://www.morepc.ru/dict/] Тематики информационные технологии в целом EN Virtual Telecommunications Access MethodVTAM …   Справочник технического переводчика

  • Метод контурных токов — Метод контурных токов  метод сокращения размерности системы уравнений, описывающей электрическую цепь. Содержание 1 Основные принципы 2 Построение системы контуров …   Википедия

  • Виртуальный собеседник — В этой статье не хватает ссылок на источники информации. Информация должна быть проверяема, иначе она может быть поставлена под сомнение и удалена. Вы можете …   Википедия

  • Абстрактный метод — Эту статью следует викифицировать. Пожалуйста, оформите её согласно правилам оформления статей. Абстрактный метод (или чистый виртуальный метод (pure virtual method часто неверно переводится как чисто виртуальный метод))  в …   Википедия

  • Фабричный метод (шаблон проектирования) — Шаблон проектирования Фабричный метод Factory Method Тип: порождающий Описан в Design Patterns Да Фабричный метод (англ. Factory Method)  порождающий шаблон проектирования, предоставляющий подклассам интерфейс для созда …   Википедия

  • Фабричный метод — (англ. Factory Method) порождающий шаблон проектирования, предоставляющий подклассам интерфейс для создания экземпляров некоторого класса. В момент создания наследники могут определить, какой класс инстанциировать. Иными словами, Фабрика… …   Википедия

  • интеллектуальный постоянный виртуальный канал — Метод организации связи с использованием гибридного логического соединения, создаваемого на основе сочетания двух типов виртуальных каналов постоянных (PVC) и коммутируемых (SVC). Администратор сети производит настройку соединений, однако… …   Справочник технического переводчика

  • Виртуальная функция — Виртуальный метод (виртуальная функция) в объектно ориентированном программировании метод (функция) класса, который может быть переопределён в классах наследниках так, что конкретная реализация метода для вызова будет определяться во время… …   Википедия

  • Сравнение C Sharp и Java — Правильный заголовок этой статьи  Сравнение C# и Java. Он показан некорректно из за технических ограничений. Сравнения языков программирования Общее сравнение Основной синтаксис Основные инструкции Массивы Ассоциативные массивы Операции со… …   Википедия


Поделиться ссылкой на выделенное

Прямая ссылка:
Нажмите правой клавишей мыши и выберите «Копировать ссылку»