Язык 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() только в дочернем классе.
По-моему, это не чистая инкапсуляция методов. Даже если мы напишем в классе Child так:
Мы все равно сможем обратиться к этому методу так:
Поэтому, это не чистая инкапсуляция. Хотя, вроде бы, если Parent позволяет обращаться к getValue(), то это значит, что нет смысла ругаться на "чистоту инкапсуляции", ибо изначально ее как таковой и не было. В общем, как-то так.
В твоем случае ты используешь метод родительского класса, поэтому все компилируется. Пример нам приведен условный, чтобы мы понимали, что это в принципе возможно
А через методы дочернего класса, удаленный метод родительского класса все ровно работает. Почему?
Еще можно скрыть родительский метод его переопределением. Ну это так, до кучи:
Не то, чтобы мы его так скрываем, мы просто создаём новый объект с тем же именем, которое уже будет скрытым (будь то метод или переменная-член), объект же родителя будет доступен при явном указании области видимости Parent::Something, его мы вообще никак не затронули физически.
В таком случае лучше всё же использовать модификаторы для наследуемого класса (private/protected) или тот же using/delete, тут намерения что-то скрыть будут яснее для читателя