Что такое виртуальный метод

от admin

Лекция 16

Пусть в классе Student есть функция get_group() , которая возвращает номер группы студента. Напомним, что её нет в Person .

Теперь рассмотрим следующую ситуацию:

При вызове p -> get_group(); получим ошибку компиляции, так как в Person не определенна функция get_group() . Нужно привести p к типу Student .

Downcast в C++ работает только через указатель или ссылку на объект базового класса.

Полиморфизм

По умолчанию в C++ работает раннее связывание имени метода с конкретным типом. Раннее связывание происходит на этапе компиляции. Таким образом в приведенном выше примере будет вызвана функция print() как функция-член класса Person .

Виртуальные методы в C++

Если же нам необходимо вызвать print() как функцию-член класса Student , мы должны реализовать позднее связывание, которое выполняется на этапе выполнения программы. Для реализации позднего связывания в C++ используются виртуальные методы.

Рассмотрим пример виртуальных функций:

Здесь, в классе Student , функция print() так же будет виртуальной — ключевое слово virtual можно не писать т.к у класса- ка данная функция уже определена как виртуальная.

Теперь, когда реализовано позднее связывание, при выполнении строки pp -> print(); из предыдущего примера, print() будет вызвана как функция-член класса Student .

Предостережение: При выполнении следующего кода:

будет выполнено раннее связывание, несмотря на то, что функция print() виртуальная.

Вывод:

Полиморфизм C++ работает только через указатели и ссылки на объекты базового класса.

Цена виртуальности

Полиморфизм в C++ реализуется с помощью таблиц виртуальных функций — Virtual Methods Table(VMT).

В каждом объекте появляется дополнительный указатель vptr на таблицу виртуальных методов. Для каждого класса создается таблица виртуальных, которая содержит адреса всех виртуальных методов этого класса и всех его предков.

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

Замечание:

В C++ нет общего главного класса-предка, как например, Object в Java/NET?. Это делается в целях повышения эффективности.

Деструкторы и полиморфизм

Как работает new: сначала выделяется память, потом вызывается конструктор.
Как работает деструктор: сначала вызывается деструктор, потом возвращается память.

Рассмотрим следующий код:

В данном случае, при выполнении delete pp; будет вызван деструктор

Person() , что плохо. Деструкторы в C++ тоже могут быть виртуальными, однако по умолчанию они таковыми не являются.

Сделаем деструктор для класса Person виртуальным:

Теперь, при освобождении памяти на которую ссылается pp , вызовется деструктор

Если для класса Student не написать деструктор, то он сгенерируется автоматическим и будем виртуальным.

Правило:

Если в классе есть хотя бы одна виртуальная функция, тогда обязательно делаем его деструктор виртуальным.

Полиморфные контейнеры и клонирование

Полиморфным называется контейнер, состоящий из полиморфных объектов. То есть объектов, в составе которых есть виртуальные методы, переопределяемые в потомках.

Представим себе некий векторный графический редактор. Все фигуры будут наследоваться от класса Shape , с виртуальным методом draw() — отображением фигуры на экран.

Заведем полиморфный контейнер, хранящий все эти разновидности фигур. Полиморфный контейнер — контейнер указателей или ссылок на объекты базового класса. (Поэтому пишем Shape* ).

Отрисовываем все объекты:

Попытаемся создать новый полиморфный контейнер на основе уже существующего. Следующий код:

не даст желаемого результата так как в v1 будут находиться указатели на те же объекты. Решением данной проблемы было бы создание виртуального конструктора, однако для конструкторов действует следующее правило:

Конструкторы в C++ не могут быть виртуальными.

Поэтому копирование полиморфного контейнера нужно производить следующим образом:

Клонирование полиморфное, так как объект должен клонировать себя, а не объект базового типа. То есть Rectangle клонирует Rectangle , Circle клонирует Circle и т.д.

Для того, чтобы такое копирование работало объявим в классе Shape функцию clone() , а за тем напишем ее реализацию для каждого графического объекта:

Обратим внимание не то, что Shape нужен только для того, чтобы его наследовать, методы в нем должны быть обязательно переопределены. Чтобы это четко обозначить, пишем:

Теперь если не переопределить функции draw() или clone() , то получим ошибку. Такие методы называются чисто виртуальными.

Система RTTI (Runtime Type Identification)

Если в классе имеются виртуальные методы, то на этапе выполнения можно идентифицировать динамический тип переменной. Для этого существует два средства:

dynamic_cast

Как это было в PascalABC.NET:

Теперь рассмотрим тот же пример в C++:

Если виртуальных методов в классе и его предках нет, то dynamic_cast будет работать как static_cast .

Если приведение возможно, тогда dynamic_cast вернет ссылку на Student , иначе nullptr . Так же в случае невозможности приведения будет сгенерировано исключение std::bad_cast . Для его обработки dynamic_cast следует поместить в блок try <…>catch(…) <…>:

В последовательности блоков catch должны в начале должны обрабатываться более специфичные исключения.

Операция typeid и структура type_info

В PascalABC.NET: object.GetType()

В С++ тип объекта определяется оператором typeid() . Данный оператор возвращает структуру type_info , которая содержит == , != , name() .

typeid() для полиморфных типов работает полиморфным образом — возвращает динамический тип объекта.

Что такое виртуальный метод

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

Те методы и свойства, которые мы хотим сделать доступными для переопределения, в базовом классе помечается модификатором virtual . Такие методы и свойства называют виртуальными.

А чтобы переопределить метод в классе-наследнике, этот метод определяется с модификатором override . Переопределенный метод в классе-наследнике должен иметь тот же набор параметров, что и виртуальный метод в базовом классе.

Например, рассмотрим следующие классы:

Здесь класс Person представляет человека. Класс Employee наследуется от Person и представляет сотруднника предприятия. Этот класс кроме унаследованного свойства Name имеет еще одно свойство — Company.

Чтобы сделать метод Print доступным для переопределения, этот метод определен с модификатором virtual . Поэтому мы можем переопределить этот метод, но можем и не переопределять. Допустим, нас устраивает реализация метода из базового класса. В этом случае объекты Employee будут использовать реализацию метода Print из класса Person:

Но также можем переопределить виртуальный метод. Для этого в классе-наследнике определяется метод с модификатором override , который имеет то же самое имя и набор параметров:

Возьмем те же самые объекты:

Виртуальные методы базового класса определяют интерфейс всей иерархии, то есть в любом производном классе, который не является прямым наследником от базового класса, можно переопределить виртуальные методы. Например, мы можем определить класс Manager, который будет производным от Employee, и в нем также переопределить метод Print.

При переопределении виртуальных методов следует учитывать ряд ограничений:

Виртуальный и переопределенный методы должны иметь один и тот же модификатор доступа. То есть если виртуальный метод определен с помощью модификатора public, то и переопредленный метод также должен иметь модификатор public.

Нельзя переопределить или объявить виртуальным статический метод.

Ключевое слово base

Кроме конструкторов, мы можем обратиться с помощью ключевого слова base к другим членам базового класса. В нашем случае вызов base.Print(); будет обращением к методу Print() в классе Person:

Переопределение свойств

Также как и методы, можно переопределять свойства:

Читать:
Почему не видно колонтитулы в ворде

В данном случае в классе Person определено виртуальное свойство Age, которое устанавливает значение, если оно больше 0 и меньше 110. В классе Employee это свойство переопределено — возраст работника должен быть не меньше 18.

Запрет переопределения методов

Также можно запретить переопределение методов и свойств. В этом случае их надо объявлять с модификатором sealed :

При создании методов с модификатором sealed надо учитывать, что sealed применяется в паре с override, то есть только в переопределяемых методах.

И в этом случае мы не сможем переопределить метод Print в классе, унаследованном от Employee.

Виртуальные методы, свойства и индексаторы

Полиморфизм предоставляет подклассу способ определения собственной версии метода, определенного в его базовом классе, с использованием процесса, который называется . Чтобы пересмотреть текущий дизайн, нужно понять значение ключевых слов virtual и override.

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

Так, если базовый класс содержит виртуальный метод и от него получены производные классы, то при обращении к разным типам объектов по ссылке на базовый класс выполняются разные варианты этого виртуального метода.

Метод объявляется как виртуальный в базовом классе с помощью ключевого слова virtual, указываемого перед его именем. Когда же виртуальный метод переопределяется в производном классе, то для этого используется модификатор override. А сам процесс повторного определения виртуального метода в производном классе называется переопределением метода. При переопределении метода — имя, возвращаемый тип и сигнатура переопределяющего метода должны быть точно такими же, как и у того виртуального метода, который переопределяется. Кроме того, виртуальный метод не может быть объявлен как static или abstract.

Переопределение метода служит основанием для воплощения одного из самых эффективных в C# принципов: динамической диспетчеризации методов, которая представляет собой механизм разрешения вызова во время выполнения, а не компиляции. Значение динамической диспетчеризации методов состоит в том, что именно благодаря ей в C# реализуется динамический полиморфизм.

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

И еще одно замечание: свойства также подлежат модификации ключевым словом virtual и переопределению ключевым словом override. Это же относится и к индексаторам.

Давайте рассмотрим пример использования виртуальных методов, свойств и индексаторов:

Виртуальные методы, свойства и индексаторы в C#

Давайте рассмотрим данный пример более подробно. В базовом классе Font инкапсулируется виртуальный метод FontInfo (), возвращающий информацию о шрифте. В производном классе FontColor данный метод переопределяется с помощью ключевого слова override, поэтому при создании экземпляра данного класса и вызова метода FontInfo() в исходную информацию возвращается помимо первоначальных данных еще и цвет шрифта. Затем данный метод вновь переопределяется в классе GradientColorFont, унаследованном от класса FontColor. Обратите внимание, что здесь переопределяется не исходный метод базового класса Font, а уже переопределенный метод класса FontColor. В этом и заключается принцип динамического полиморфизма!

Так же обратите внимание, что в данном примере используется виртуальное свойство color, принцип использования которого аналогичен использованию виртуального метода.

What are Virtual Methods?

The Virtual Modifier is used to mark that a method\property(ect) can be modified in a derived class by using the override modifier.

cgreeno's user avatar

Virtual allows an inheriting class to replace a method that the base class then uses.

When you run the Program your output will be:

Notice how even though Widget called the Action() method defined at the Thingy level, internally Thingy called Widget’s StepA() method.

The basic answer is it gives inheritors of a class more flexibility. Of course, you’ve got to engineer your class well or it could weak havoc.

Ludington's user avatar

A virtual method is a type of method where the actual method calls depends on the runtime type of the underlying object.

A non-virtual method is a type of method where the actual method called depends on the reference type of the object at the point of method invocation.

  • It is an error to use the virtual modifier on a static property.
  • A virtual inherited property can be overridden in a derived class by including a property declaration that uses the override modifier.

Russ Cam's user avatar

Virtual methods are similar to abstract methods in base classes except their implementation on derived classes is optional. Also you could put logic in virtual method and override these in derived classes.

Even if you don’t plan to derive from the class, marking the method virtual may be necessary in order to mock the class. Some mocking frameworks only allow you to mock virtual methods. Note that methods implementing an interface are virtual implicitly.

I use RhinoMocks which has this restriction and have taken to marking my methods virtual by default for just this reason. For me, this is probably the biggest reason to use virtual methods as the cases where inheritance comes into play are much less frequent.

A short question, a short answer! Qualify your method as «virtual» if you think you will inherit of the class it belongs to.

A longer answer: «virtual enables you to override, to give another meaning of your method in a derived class.

Henry's user avatar

In order to be able to override it in inheriting classes.

Check out the MSDN entry for the keyword. That explains it more in depth.

In C#, for overriding the base class method in derived class, you have to declare base class method as virtual and derived class method as override as shown below:

You can also mix the method hiding and method overriding by using virtual and new keyword since the method of a derived class can be virtual and new at the same time. This is required when you want to further override the derived class method into next level as I am overriding Class B, Test() method in Class C as shown below:

GOLDEN WORDS: The virtual keyword is used to modify a method, property, indexer, or event declared in the base class and allow it to be overridden in the derived class.

The override keyword is used to extend or modify a virtual/abstract method, property, indexer, or event of base class into derived class.

The new keyword is used to hide a method, property, indexer, or event of base class into derived class.

Related Posts