Студопедия
Случайная страница | ТОМ-1 | ТОМ-2 | ТОМ-3
АрхитектураБиологияГеографияДругоеИностранные языки
ИнформатикаИсторияКультураЛитератураМатематика
МедицинаМеханикаОбразованиеОхрана трудаПедагогика
ПолитикаПравоПрограммированиеПсихологияРелигия
СоциологияСпортСтроительствоФизикаФилософия
ФинансыХимияЭкологияЭкономикаЭлектроника

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

Наследование классов | Применение наследования | Использование объектов производного класса в качестве объектов базового класса | Dynamic_cast <тип *> (выражение). | Информация о типе во время выполнения (RTTI) (лучше почитать после обсуждения виртуальных функций). | Абстрактные базовые классы и чисто виртуальные функции | Множественное наследование |


Читайте также:
  1. III. B. Функции слова ONE
  2. Other Functions of Money. Другие функции денег
  3. V) Массивы и функции
  4. Абстрактные базовые классы и чисто виртуальные функции
  5. Абстрактные базовые классы и чисто виртуальные функции.
  6. Аппроксимация 1s –функции электрона в атоме водорода двумя гауссовыми функциями
  7. Банковская система, ее структура. Функции коммерческих банков.

 

Предположим, что функция с именем func () определена в производном классе и замещает функцию в базовом классе с той же самой сигнатурой. Эта функция производного класса вызывается, только если к ней обратиться из (через имя) объекта производного класса. Поскольку объект производного класса, на который ссылаются посредством ссылки на базовый класс, или через указатель на базовый класс, ведет себя так, как будто он является объектом базового класса, то, при обращении к func () через указатель this, вызывается функция func (), определенная в базовом классе. Аналогично, если переменная ссылочного типа на базовый класс ссылается на объект производного класса, или объект производного класса передан в функцию (по значению или по ссылке), на месте формального параметра, описанного как базовый класс, то будет использована функция func () базового класса (при вызове, конечно). Пример ниже демонстрирует сказанное.

 

Пример 15-0.

 

#include <iostream.h>

 

class Base

{ public: void func() {cout << "Base class function.\n";} };

 

class Derived: public Base

{ public: void func() {cout << "Derived class function.\n";} };

 

void foo(Base b)

{ b.func(); }

 

int main()

{

Derived d;

Base b;

Base * p = &d;

Base& br = d;

 

b = d;

b.func();

d.func();

p -> func();

foo(d);

br.func();

 

return 0;

}

 

Программа выводит:

 

Base class function

Derived class function.

Base class function.

Base class function.

Base class function.

 

В некоторых приложениях может потребоваться, чтобы указатель на объект базового класса использовался для указания на объект, который может быть и из этого базовом класса и в одном из его производных (фразу “объект из класса” здесь надо понимать как “экземпляр класса”), а программе необходимо обращение к замещаемой функции, определенной в классе, которому объект действительно принадлежит. Эта проблема доступа к методам, переопределенным в производных классах, через указатель на бвзовый класс разрешается в C++ использованием виртуальных функций (методов). Для того, чтобы сделать некоторый метод виртуальным, его нужно объявить со спецификатором virtual, как в базовом, так и в производных классах (но в производном писать явно virtual не обязательно, хотя и рекомендуется: метод будет воспринят таковым автоматически во всех производных классах). Когда функция специфицирована как виртуальная, то такая замещаемая функция вызывается в соответствии с истинной идентичностью объекта (т.е., в соответствии с действительной принадлежностью конкретного объекта определенному классу). Если в предыдущем примере специфицировать func() как виртуальную, то программа выведет:

 

Base class function

Derived class function

Derived class function

Base class function

Derived class function

 

Если func() – виртуальная, а функции foo() передать как аргумент ссылку br на Base (инициализированную производным объектом d), то все последние четыре строки вывода должны быть "Derived class function". Т.е., сказанное выше относится также и к вызову виртуальных методов по ссылке на базовый класс.

По отношению к виртуальным методам компилятор применяет стратегию динамического, или позднего, связывания: на этапе компиляции (на шаге компоновки) вызов метода не идентифицируется, а компилятор передает ответственность за принятие решения о выборе конкретного метода программе, делающей это во время выполнения, когда уже точно известно, каков тип объекта, на который указывает наш указатель или ссылается ссылка. Разрешение ссылок на виртуальные методы на шаге редактирования связей (раннее связывание) не выполняется, а откладывается до времени выполнения программы (позднее связывание).

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

Общие правила (см. Т. Павловская, стр.206) описания и использования виртуальных методов:

Чисто виртуальный метод содержит признак = 0 вместо тела, например:

virtual void f(int) = 0;

Чисто виртуальный метод должен переопределяться в производном классе (возможно, опять как чисто виртуальный).

Четких критериев, по которым метод следует делать виртуальным, не существует. Класс, содержащий хотя бы одну виртуальную функцию, называют полиморфным. Форма полиморфизма – полиморфизм типов. При обращении к виртуальным функциям, мы должны понимать различие между объявленным типом объекта, указателя или ссылки и действительным типом во время выполнения (run-time). Объявленный тип часто называют статическим типом, а действительный, реальный тип – динамическим типом. Например:

struct base { virtual void func();}; struct derived: base { virtual void func(); // переопределение}; base* b = new derived; // статический тип b есть base*. // динамический тип - derived*.b->func(); // вызов динамический_тип::func()

Замечания:

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

 

(2) Когда объект производного класса вызывает метод базового класса, указатель this в этом методе указывает на объект производного класса. Поэтому, если некоторая виртуальная функция вызывается в этом методе, то вызывается версия функции производного класса.

 

(3) Даже если функция объявлена как виртуальная, иногда, Вам еще все же нужно использовать функцию базового класса для объекта из производного класса. Это можно сделать используя имя базового класса и операцию ::.

 

(4) Для классов с виртуальными функциями, при уничтожении указателя на объект базового класса вызывается деструктор базового класса. Но этот указатель, на самом деле, может указывать на объект производного класса. Объект производного класса “содержит” объект базового класса. Деструктор базового класса разрушает только объект базового класса, а не весь объект производного класса. Для того, чтобы уничтожить объект производного класса, деструктор производного класса должен быть вызван, когда применяется операция delete для уничтожения указателя на базовый класс, указывающий на объект производного класса. Это означает, что деструктор базового класса должен быть виртуальным. Поэтому, если Вы определяете класс и производные от него, то деструктор лучше сделать виртуальным. Но “лишние” виртуальные деструкторы тоже не нужны, так что правило такое: если класс имеет хотя бы одну виртуальную функцию, то деструктор класса следует делать виртуальным.

 

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

 

Динамическое связывание наделяет программу способностью “позаботиться о коде, который будет добавлен в будущем”. Пусть, например, программа вызывает виртуальную функцию через ссылку на базовый объект. Тогда реально вызывается член-функция из класса этого объекта: базового или производного. Теперь предположим, что в будущем мы определим новый производный класс, в котором эта виртуальная функция будет замещена. Тогда может случиться, что объект, на который мы ссылаемся с помощью нашей ссылки, будет объектом нового производного класса. Если это так, то будет вызвана версия функции из нового производного класса. И, следовательно, нет необходимости в изменении старого кода.

 

 


Дата добавления: 2015-11-16; просмотров: 52 | Нарушение авторских прав


<== предыдущая страница | следующая страница ==>
Конструкторы, деструктор и перегрузка опрации присваивания в производном классе.| Плата за полиморфизм

mybiblioteka.su - 2015-2024 год. (0.008 сек.)