Однією з найбільших переваг використання дочірніх класів є можливість повторного використання вже написаного коду. Ми можемо наслідувати функціонал батьківського класу, а потім додати свій функціонал/змінити існуючий функціонал/приховати непотрібні частини батьківського функціоналу. На цьому і на наступних уроках ми розглянемо, як це все зробити.
По-перше, почнемо з класу Parent:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> class Parent { protected: int m_value; public: Parent(int value) : m_value(value) { } void identify() { std::cout << "I am a Parent\n"; } }; |
Тепер створимо дочірній клас, який наслідуватиме клас Parent. Оскільки ми хочемо мати можливість встановити значення m_value
при ініціалізації об’єктів дочірнього класу, то зробимо так, щоб конструктор класу Child викликав конкретний конструктор класу Parent в списку ініціалізації:
1 2 3 4 5 6 7 8 |
class Child: public Parent { public: Child(int value) : Parent(value) { } }; |
Додання нового функціоналу в дочірній клас
У прикладі, наведеному вище, оскільки ми маємо доступ до вихідного коду класу Parent, то ми можемо додати новий функціонал безпосередньо в клас Parent, якщо захочемо.
Є випадки, коли ми маємо доступ до батьківського класу, але не хочемо його змінювати. Наприклад, ми тільки що купили бібліотеку коду у стороннього постачальника, але нам потрібен додатковий функціонал. Ми можемо додати його в вихідний код бібліотеки, але це буде не кращим рішенням. Що, якщо прийде оновлення? Після оновлення нам заново доведеться вручну перенести весь код, що займе час і, крім того, після оновлення залишається ризик, що наш старий код вже не працюватиме так, як потрібно з кодом бібліотеки.
Також є випадки, коли змінити батьківський клас неможливо. Наприклад, ми не можемо змінити код, який є частиною Стандартної бібліотеки C++. Але ми можемо успадкувати класи з цієї бібліотеки, а потім додати потрібний нам функціонал в наші дочірні класи. Те ж саме стосується сторонніх бібліотек, де нам надані заголовки, але код поставляється попередньо скомпільований.
У будь-якому випадку кращим рішенням є написання власного батьківського класу і додання потрібного нам функціоналу в наші дочірні класи.
Один нюанс з класом Parent полягає в доступі інших об’єктів до m_value
. Ми можемо це виправити, додавши функцію доступу в клас Parent, але, заради прикладу, додамо геттер в клас Child. Оскільки m_value
оголошений як protected в класі Parent, то Child має прямий доступ до нього.
Додання нового функціоналу в дочірній клас виконується як зазвичай:
1 2 3 4 5 6 7 8 9 10 |
class Child: public Parent { public: Child(int value) : Parent(value) { } int getValue() { return m_value; } }; |
Тепер інші об’єкти ззовні зможуть викликати getValue() через об’єкт класу Child для доступу до m_value
:
1 2 3 4 5 6 7 |
int main() { Child child(7); std::cout << "child has value " << child.getValue() << '\n'; return 0; } |
Результат:
child has value 7
Це очевидно, що об’єкти класу Parent не мають доступу до методу getValue() в Child. Наступне не спрацює:
1 2 3 4 5 6 7 |
int main() { Parent parent(7); std::cout << "parent has value " << parent.getValue() << '\n'; return 0; } |
Це пов’язано з тим, що в класі Parent немає методу getValue(). Метод getValue() належить класу Child. А оскільки Child є дочірнім класу Parent, то Child має доступ до членів Parent, а Parent не має доступу ні до чого в класі Child.