Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
+ООП_Навч_посібник.doc
Скачиваний:
7
Добавлен:
01.07.2025
Размер:
6.58 Mб
Скачать

15.4. Успадкування декількох базових класів

Похідний клас може успадковувати два або більше базових класів. Наприклад, у цій короткій програмі клас derived успадковує обидва класи baseA і baseB.

Код програми 15.8. Демонстрація механізму успадкування декількох базових класів

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

class baseA { // Оголошення базового класу

protected:

int x;

public:

void showXzm() { cout << x << "\n";}

};

class baseB { // Оголошення базового класу

protected:

int y;

public:

void showYzm() { cout << y << "\n";}

};

// Успадкування двох базових класів.

// Оголошення похідного класу

class derived : public baseA, public baseB { // Оголошення базового класу

public:

void setXY(int izm, int jzm) {x = izm; y = jzm;}

};

int main()

{

derived D_ob; // Створення об'єкта класу

D_ob.setXY(10, 20); // член класу derived

D_ob.showXzm(); // функція з класу baseA

D_ob.showYzm(); // функція з класу baseB

getch(); return 0;

}

Як видно з цього прикладу, щоб забезпечити успадкування декількох базових класів, необхідно через кому перерахувати їх імена у вигляді списку. При цьому потрібно вказати специфікатор доступу для кожного успадкованого базового класу.

15.5. Використання конструкторів і деструкторів під час реалізації механізму успадкування

Під час реалізації механізму успадкування зазвичай виникають два важливі запитання, пов'язані з використання конструкторів і деструкторів:

  • коли викликаються конструктори і деструктори базового і похідного класів?

  • як можна передати параметри конструктору базового класу?

Відповіді на ці запитання викладено нижче у цьому підрозділі.

15.5.1. Прядок виконання конструкторів і деструкторів

Базовий і/або похідний клас може містити конструктор і/або деструктор. Важливо розуміти порядок, у якому виконуються ці функції під час створення об'єкта похідного класу і його (об'єкта) руйнування. Для розуміння сказаного розглянемо таку коротку програму.

Код програми 15.9. Демонстрація порядку виконання конструкторів і деструкторів

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

class baseClass { // Оголошення базового класу

public:

baseClass() { cout << "Створення baseClass-об'єкта.\n";}

~baseClass() { cout << "Руйнування baseClass-об'єкта.\n";}

};

// Оголошення похідного класу

class derived : public baseClass {

public:

derived() { cout << "Створення derived-об'єкта.\n";}

~derived() { cout << "Руйнування derived-об'єкта.\n";}

};

int main()

{

derived D_ob; // Створення об'єкта класу

// Ніяких дій, окрім створення і руйнування об'єкта D_ob.

getch(); return 0;

}

Як зазначено у коментарях для функції main(), ця програма тільки створює і відразу руйнує об'єкт D_ob, який має тип derived. У процесі виконання програма відображає на екрані такі результати:

Створення baseClass-об'єкта.

Створення derived-об'єкта.

Руйнування derived-об'єкта.

Руйнування baseClass-об'єкта.

Аналізуючи отримані результати, бачимо, що спочатку виконується конструктор класу baseClass, а за ним – конструктор класу derived. Потім (через негайне руйнування об'єкта ob у цій програмі) викликається деструктор класу derived, а за ним – деструктор класу baseClass.

Результати описаного вище експерименту можна узагальнити таким чином. Під час створення об'єкта похідного класу спочатку викликається конструктор базового класу, а за ним – конструктор похідного класу. Під час руйнування об'єкта похідного класу спочатку викликається його "рідний" конструктор, а за ним – конструктор базового класу.

Конструктори викликаються у порядку походження класів, а деструктори – у зворотному порядку.

Цілком логічно, що функції конструкторів виконуються у порядку походження їх класів. Оскільки базовий клас "нічого не знає" ні про який похідний клас, операції з ініціалізації, які йому потрібно виконати, не залежать від операцій ініціалізації, що виконуються похідним класом, але, можливо, створюють попередні умови для подальшої роботи. Тому конструктор базового класу повинен виконуватися першим.

Така ж логіка простежується і у тому, що деструктори виконуються у порядку, зворотному порядку походження класів. Оскільки базовий клас знаходиться в основі похідного класу, то руйнування першого передбачає руйнування другого. Отже, деструктор похідного класу є сенс викликати до того, як об'єкт буде повністю зруйнований.

При розширеній ієрархії класів (тобто за ситуації, коли похідний клас стає базовим класом для ще одного похідного) застосовується таке загальне правило: конструктори викликаються у порядку походження класів, а деструктори – у зворотному порядку. Для розуміння сказаного розглянемо таку коротку програму.

Код програми 15.10. Демонстрація порядку виконання конструкторів і деструкторів при розширеній ієрархії класів

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

class baseClass { // Оголошення базового класу

public:

baseClass() { cout << "Створення baseClass-об'єкта.\n";}

~baseClass() { cout << "Руйнування baseClass-об'єкта.\n";}

};

// Оголошення похідного класу

class derivedA : public baseClass {

public:

derivedA() { cout << "Створення derivedA-об'єкта.\n";}

~derivedA() { cout << "Руйнування derivedA-об'єкта.\n";}

};

// Оголошення похідного класу

class derivedB : public derivedA {

public:

derivedB() { cout << "Створення derivedB-об'єкта.\n";}

~derivedB() { cout << "Руйнування derivedB-об'єкта.\n";}

};

int main()

{

derivedB B_ob; // Створення об'єкта класу

// Створення і руйнування об'єкта B_ob.

getch(); return 0;

}

У процесі виконання цієї програми на моніторі будуть відображені такі результати:

Створення baseClass-об'єкта.

Створення derivedA-об'єкта.

Створення derivedB-об'єкта.

Руйнування derivedB-об'єкта.

Руйнування derivedA-об'єкта.

Руйнування baseClass-об'єкта.

Те саме загальне правило застосовується і у ситуаціях, коли похідний клас успадковує декілька базових класів.

Код програми 15.11. Демонстрація порядку виконання конструкторів і деструкторів під час успадкування декількох базових класів

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

// Оголошення базового класу

class baseA {

public:

baseA() { cout << "Створення baseA-об'єкта.\n";}

~baseA() { cout << "Руйнування baseA-об'єкта.\n";}

};

// Оголошення базового класу

class baseB {

public:

baseB() { cout << "Створення baseB-об'єкта.\n";}

~baseB() { cout << "Руйнування baseB-об'єкта.\n";}

};

// Оголошення похідного класу

class derived : public baseA, public baseB {

public:

derived() { cout << "Створення derived-об'єкта.\n";}

~derived() { cout << "Руйнування derived-об'єкта.\n";}

};

int main()

{

derived D_ob; // Створення об'єкта класу

// Створення і руйнування об'єкта D_ob.

getch(); return 0;

}

У процесі виконання цієї програми генеруються такі результати:

Створення baseA-об'єкта.

Створення baseB-об'єкта.

Створення derived-об'єкта.

Руйнування derived-об'єкта.

Руйнування baseB-об'єкта.

Руйнування baseA-об'єкта.

Як бачите, конструктори викликаються у порядку походження їх класів, зліва направо, у порядку їх задавання у переліку успадкування для класу derived. Деструктори викликаються у зворотному порядку, справа наліво. Це означає, що якби клас baseB знаходився перед класом baseA у переліку класу derived, тобто відповідно до такої настанови:

class derived : public baseB, public baseA {,

то результати виконання попередньої програми були б такими:

Створення baseB-об'єкта.

Створення baseA-об'єкта.

Створення derived-об'єкта.

Руйнування derived-об'єкта.

Руйнування baseA-об'єкта.

Руйнування baseB-об'єкта.