Мова C++ надає можливість змінити специфікатор доступу батьківського члена в дочірньому класі. Це робиться за допомогою “using-оголошення”. Наприклад, розглянемо наступний клас Parent:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> class Parent { private: int m_value; public: Parent(int value) : m_value(value) { } protected: void printValue() { std::cout << m_value; } }; |
Оскільки Parent::printValue() оголошений як protected, то він доступний тільки іншим членам Parent і своїм дочірнім класам. Для інших об’єктів доступ до нього закритий.
Визначимо клас Child, який змінює специфікатор доступу printValue() з protected на public:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
class Child: public Parent { public: Child(int value) : Parent(value) { } // Parent::printValue є protected, тому доступ до нього не є відкритим для всіх об'єктів. // Але ми можемо це виправити за допомогою "using-оголошення" using Parent::printValue; // зверніть увагу, тут немає ніяких дужок }; |
Це означає, що наступний код виконається без помилок:
|
1 2 3 4 5 6 7 8 |
int main() { Child child(9); // Метод printValue() є public в класі Child, тому все добре child.printValue(); // виведеться 9 return 0; } |
Тут є дві примітки:
По-перше, ви можете змінити специфікатори доступу тільки для тих членів батьківського класу, до яких є доступ у дочірнього класу. Ви не зможете змінити специфікатор доступу члена батьківського класу з private на protected або public, оскільки дочірній клас не має доступу до private-членів батьківського класу.
По-друге, починаючи з C++11 використання “using-оголошення” є кращим способом зміни специфікаторів доступу. Однак ви також можете використовувати “access-оголошення”. Це працює ідентично “using-оголошенню”, тільки без ключового слова using. Зараз цей спосіб вважається застарілим, але, переглядаючи старий код, ви можете побачити “access-оголошення”, тому про це варто знати.
Приховування батьківських методів в дочірньому класі
У мові C++ неможливо видалити або обмежити функціонал батьківського класу, крім як за допомогою безпосередньої зміни вихідного коду. Однак в дочірньому класі ви можете приховати функціонал, який існує в батьківському класі, так щоб до нього не можна було отримати доступ через об’єкти дочірнього класу. Це робиться шляхом зміни відповідних специфікаторів доступу.
Наприклад, ви можете зробити закритим відкритий член батьківського класу:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <iostream> class Parent { public: int m_value; }; class Child : public Parent { private: using Parent::m_value; public: Child(int value) // Ми не можемо ініціалізувати m_value, оскільки це член класу Parent (Parent повинен ініціалізувати m_value) { // Але ми можемо присвоїти значення m_value = value; } }; int main() { Child child(9); // Наступне не спрацює, оскільки m_value був перевизначений як private std::cout << child.m_value; return 0; } |
Це дозволяє інкапсулювати дані батьківського класу в дочірньому класі. В якості альтернативи можна використати спадкування типу private, що призведе до того, що всі успадковані public- і protected-члени класу Parent стануть private в класі Child.
Ви також можете закрити батьківські методи в дочірньому класі, використовуючи ключове слово delete:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#include <iostream> class Parent { private: int m_value; public: Parent(int value) : m_value(value) { } int getValue() { return m_value; } }; class Child : public Parent { public: Child(int value) : Parent(value) { } int getValue() = delete; // робимо цей метод недоступним }; int main() { Child child(9); // Наступне не спрацює, тому що getValue() видалений std::cout << child.getValue(); return 0; } |
Таким чином, компілятор скаржитиметься на нашу спробу виклику методу getValue() через об’єкт класу Child. Однак через об’єкт батьківського класу все працюватиме, тому що ми «видалили» getValue() тільки в дочірньому класі.
