Лекция 5.

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

Виртуальная функция (virtual function) — это функция-член класса, объявленная в базовом классе и переопределенная в производном. Для ее объявления используется ключевое слово virtual.

Пример 1. Вызов виртуальной функции при помощи указателя на базовый класс:

class base {

public:

virtual void vfunc() {

cout << “Функция vfunc() из класса base\n”

}

};

class derived1: public base {

public:

void vfunc() {

cout << “Функция vfunc() из класса derived1\n”

}

};

class derived2: public base {

public:

void vfunc() {

cout << “Функция vfunc() из класса derived2\n”

}

};

int main() {

base *p, b;

derived1 d1;

derived2 d2;


// Указатель на объект базового класса:

p = &b;

p→vfunc(); // Вызов функции vfunc() из класса base


// Указатель на объект класса derived1:

p = &d1;

p→vfunc(); // Вызов функции vfunc() из класса derived1


// Указатель на объект класса derived2:

p = &d2;

p→vfunc(); // Вызов функции vfunc() из класса derived2


return 0;

}


Эта программа выводит на экран следующие строки:


Функция vfunc() из класса base

Функция vfunc() из класса derived1

Функция vfunc() из класса derived2


В примере 1 внутри класса base объявлена виртуальная функция vfunc().

Эта функция будет виртуальной для derived1 и derived2 даже если в них она объявлена без ключего слова virtual.

Для виртуальной функции вариант вызываемой функции определяется типом объекта, на который ссылается указатель p.

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


Вопрос. Чем отличаются виртуальные функции от перегруженных?

Ответ.

1) У перегруженных функций совпадают только имена, а количество и/или типы параметров должны отличаться, чтобы компилятор мог выбрать нужный вариант функции. У виртуальных функций же должны совпадать и имена, и количество параметров, и типы параметров.

2) Виртуальные функции не могут быть статическими членами класса.


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


Пример 2. Вызов виртуальной функции через ссылку на объект базового класса:

void f(base &r) {

r.func();

}

int main() {

base b;

derived d1;

derived d2;


f(b); // Функции f() передается объект класса base

f(d1); // Функции f() передается объект класса derived1

f(d2); // Функции f() передается объект класса derived2


return 0;

}


Эта программа выводит на экран те же сообщения, что и программа из примера 1.


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


Пример 3. Наследование атрибута virtual:


class base {

public:

virtual void vfunc() {

cout << “Функция vfunc() из класса base\n”

}

};

class derived1: public base {

public:

void vfunc() {

cout << “Функция vfunc() из класса derived1\n”

}

};

class derived2: public derived1 {

public:

// Функция vfunc() остается виртуальной:

void vfunc() {

cout << “Функция vfunc() из класса derived2\n”

}

};

int main() {

base *p, b;

derived1 d1;

derived2 d2;


// Указатель на объект базового класса:

p = &b;

p→vfunc(); // Вызов функции vfunc() из класса base


// Указатель на объект класса derived1:

p = &d1;

p→vfunc(); // Вызов функции vfunc() из класса derived1


// Указатель на объект класса derived2:

p = &d2;

p→vfunc(); // Вызов функции vfunc() из класса derived2


return 0;

}


Результатом работы этой программы будет печать тех же строк, что и в примерах 1 и 2.


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


Пример 4. Выведенный класс derived2 не замещает функцию vfunc():

class base {

public:

virtual void vfunc() {

cout << “Функция vfunc() из класса base\n”

}

};

class derived1: public base {

public:

void vfunc() {

cout << “Функция vfunc() из класса derived1\n”

}

};

class derived2: public base {

public:

// Функция vfunc() не замещается в классе derived2, используется версия из класса base

};

int main() {

base *p, b;

derived1 d1;

derived2 d2;


// Указатель на объект базового класса:

p = &b;

p→vfunc(); // Вызов функции vfunc() из класса base


// Указатель на объект класса derived1:

p = &d1;

p→vfunc(); // Вызов функции vfunc() из класса derived1


// Указатель на объект класса derived2:

p = &d2;

p→vfunc(); // Вызов функции vfunc() из класса base


return 0;

}


Эта программа выводит на экран следующие строки:


Функция vfunc() из класса base

Функция vfunc() из класса derived1

Функция vfunc() из класса base


Чисто виртуальные функции


Чисто виртуальная функция (pure virtual function) – виртуальная функция, не имеющая опеределения в базовом классе.

Для страховки от неправильного вызова ее часто объявляют равной нулю:


virtual <тип> <имя функции> (<список параметров>) = 0;


Пример 5. Ситуация, когда определение виртуальной функции в базовом классе не имеет смысла.

class number {

protected:

int val;

public:

void setval(int i) { val = i; }

vurtual void show() = 0; // Функция show() является чисто виртуальной

};

class hextype: public number {

public:

void show() {

cout << hex << val << “\n”;

}

};

class dectype: public number {

public:

void show() {

cout << val << “\n”;

}

};

class octtype: public number {

public:

void show() {

cout << oct << val << “\n”;

}

};

int main() {

dectype d;

hextype h;

octtype o;


d.setval(20);

d.show(); // Выводит десятичное число 20


h.setval(10);

h.show(); // Выводит шестнадцатеричное число 14


o.setval(20);

o.show(); // Выводит восьмеричное число 24


return 0;

}


Абстрактные классы.

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

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

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

Указатели и ссылки на абстрактный класс создать можно, это используется для поддержки динамического полиморфизма.