77.16K
Category: programmingprogramming

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

1.

Занятие 5

2.

Виртуальные методы
• В С++ реализован механизм позднего
связывания, когда разрешение ссылок на
функцию происходит на этапе выполнения
программы в зависимости от конкретного
типа объекта, вызвавшего функцию. Этот
механизм реализован с помощью
виртуальных методов.

3.

• виртуальным называется метод, ссылка на
который разрешается на этапе выполнения
программы (перевод красивого
английского слова virtual – всего-навсего
«фактический», то есть ссылка разрешается
по факту вызова).

4.

• К механизму виртуальных функций обращаются в
тех случаях, когда в каждом производном классе
требуется свой вариант некоторой компонентной
функции. Классы, включающие такие функции,
называются полиморфными и играют особую роль в
ООП.
• Виртуальные функции предоставляют механизм
позднего (отложенного) или динамического
связывания. Любая нестатическая функция базового
класса может быть сделана виртуальной, для чего
используется ключевое слово virtual.

5.

Пример.
class base
{
public:
virtual void print(){cout<<“\nbase”;}
...
};
class dir : public base
{
public:
void print(){cout<<“\ndir”;}
};
void main()
{
base B,*bp = &B;
dir D,*dp = &D;
base *p = &D;
bp –>print(); // base
dp –>print(); // dir
p –>print(); // dir
}

6.

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

7.

Полиморфный тип (класс),
• Тип данных (класс), содержащий хотя бы одну
виртуальную функцию, называется полиморфным
типом (классом), а объект этого типа –
полиморфным объектом.
• при вызове виртуальной функции через указатель на
полиморфный объект осуществляется динамический
выбор тела функции в зависимости от текущего тела
объекта, а не от типа указателя. Тело функции в таком
случае выбирается на этапе выполнения, а не
компиляции.
• Виртуальная функция объявляется описателем virtual.
• Во всех классах-наследниках наследуемая
виртуальная функция остается таковой (виртуальной).

8.

Пример:
#include <iostream>
using namespace std;
class A {
public:
virtual void f (int x) {
cout << "A::f" << '\n';
}
};
class C: public A{
public:
void f (int x) {
cout << "C::f" << '\n';
}
};
int main(){
A a1;
A* pa;
C c1;
C* pc;
pc = & c1;
pc -> f (1); // C::f
pa = pc;
pa -> f (1); // C::f
pc = (C*) & a1;
pc -> f (1); // A::f
return 0;
}

9.

Чистая виртуальная функция
функция вида:
Такая форма записи функции означает, что
данная функция (точнее – метод
класса) не имеет тела, описывающего ее
алгоритм.

10.

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

11.

Пример.
class Base{
public:
Base(); // конструктор по умолчанию
Base(const Base&); // конструктор копирования
virtual ~Base(); // виртуальный деструктор
virtual void Show()=0; // чистая виртуальная функция
// другие чистые виртуальные функции
protected: // защищенные члены класса
private:
// часто остается пустым, иначе будет мешать будущим разработкам
};
class Derived: virtual public Base{
public:
Derived(); // конструктор по умолчанию
Derived(const Derived&); // конструктор копирования
Derived(параметры);// конструктор с параметрами
virtual ~Derived(); // виртуальный деструктор
void Show(); // переопределенная виртуальная функция
// другие переопределенные виртуальные функции
// другие перегруженные операции
protected:
// используется вместо private, если ожидается наследование
private:
// используется для деталей реализации
};

12.

• Объект абстрактного класса не может быть
формальным параметром функции, однако
формальным параметром может быть указатель на
абстрактный класс. В этом случае появляется
возможность передавать в вызываемую функцию в
качестве фактического параметра значение
указателя на производный объект, заменяя им
указатель на абстрактный базовый класс. Таким
образом мы получаем полиморфные объекты.
• Абстрактный метод может рассматриваться как
обобщение переопределения. В обоих случаях
поведение родительского класса изменяется для
потомка. Для абстрактного метода, однако,
поведение просто не определено. Любое
поведение задается в производном классе.

13.

Таблица виртуальных функций
(Virtual Function Table)
• Для каждого класса, содержащего виртуальные методы, или
унаследованного от класса с виртуальными методами, создается
таблица виртуальных функций. Эта таблица предназначена для
вызова нужных реализаций виртуальных методов во время
исполнения программы. При создании экземпляра класса, указатель
на VFT этого класса помещается в самое начало созданного объекта.
• Как известно, конструирование объекта происходит поэтапно и
начинается созданием объекта самого первого класса в иерархии
наследования. Во время этого процесса перед вызовом конструктора
каждого класса указатель на VFT устанавливается равным указателю
на VFT текущего конструируемого класса. Например, у нас есть 3
класса: A, B, C (B наследуется от A, C наследуется от B). При создании
экземпляра С, произойдут 3 последовательных вызова конструкторов:
сначала A(), затем B(), и в конце C(). Перед вызовом
конструктора A() указатель на VFT будет указывать на таблицу
класса A, перед вызовом B() он станет указывать на таблицу
класса B() и т.д. Аналогичная ситуация при вызове деструкторов,
только указатель будет меняться от таблицы самого младшего класса
к самому старшему.
• Из этого факта следует правило: в конструкторах и деструкторах
нельзя вызывать виртуальные методы.

14.

• Механизм виртуальных функций в ООП используется
для реализации полиморфизма: создания метода,
предназначенного для работы с различными объектами
за счет механизма позднего связывания (late binding).
• Виртуальные функции объявляются в базовом и
производных классах с ключевым словом virtual. При
этом каждый объект класса, управляемого из базового
класса с виртуальными функциями, имеет указатель
на vmtbl (virtual method table), содержащую адреса
виртуальных функций. Эти адреса устанавливаются в
адреса нужных для данного объекта функций во время
выполнения.
• В отличие от перегружаемых функций виртуальные
объявляются в порожденных классах с тем же именем,
возвращаемым значением и типом аргументов. Если
различны типы аргументов, виртуальный механизм
игнорируется. Тип возвращаемого значения
переопределить нельзя.

15.

• Основная идея в использовании виртуальной
функции состоит в следующем: она может
быть объявлена в базовом классе, а затем
переопределена в каждом производном
классе. При этом доступ через указатель на
объект базового класса осуществляется к этой
функции из базового класса, а доступ через
указатель на объект производного класса – из
производного класса. То же происходит при
передаче функции объекта производного
класса, если аргумент объявлен как базовый
класс.

16.

Массив объектов разных классовпотомков
• Создать абстрактный класс-родитель
• Создать массив указателей на абстрактный
класс
• Для каждого элемента массива выделить
память для объекта класса-потомка

17.

#include <iostream>
using namespace std;
class A{
public:
A(){}
virtual void print()=0;};
class A1: public A{
public:
A1(){}
void print(){cout<<"A1 ";}
};
class A2: public A{
public:
A2(){}
void print(){cout<<"A2 ";}
};
class A3: public A{
public:
A3(){}
void print(){cout<<"A3 ";}
};
int main(){
//A Array[10];//Выдаст ошибку:
попытка создать экземпляры
абстрактного класса
A* Ar[10];//массив указателей
Ar[0]=new A1();
Ar[1]=new A1();
Ar[2]=new A2();
Ar[3]=new A3();
Ar[4]=new A1();
Ar[5]=new A1();
Ar[6]=new A2();
Ar[7]=new A3();
Ar[8]=new A3();
Ar[9]=new A1();
for(int i=0;i<10;i++) Ar[i]->print();
delete []Ar; // чистим память
return 0;
}

18.

#include <iostream>
int main(){
using namespace std;
//A Array[10];//Выдаст ошибку:
попытка создать экземпляры
class A{
абстрактного класса
public:
A* Ar[10];//массив указателей
A(){}
virtual void print()=0;};
Ar[0]=new A1();
class A1: public A{
КлассыМассив
Ar[1]=new A1();
Потомки
указателей
public:
Ar[2]=new A2();
на
A1(){}
Ar[3]=new A3();
объекты
void print(){cout<<"A1 ";}
Ar[4]=new A1();
разных
};
классов
Ar[5]=new A1();
class A2: public A{
Ar[6]=new A2();
public:
Ar[7]=new A3();
A2(){}
Демонстрация
Ar[8]=new A3();
void print(){cout<<"A2 ";}
работы
Ar[9]=new A1();
};
class A3: public A{
for(int i=0;i<10;i++) Ar[i]->print();
public:
delete []Ar; // чистим память
A3(){}
return 0;
void print(){cout<<"A3 ";}
}
};
English     Русский Rules