Заголовок статьи на самом деле представляет собой не одно утверждение, а два, хотя оба они известны:
- В C++ единицей инкапсуляции является класс – а не отдельный объект ([Stroustrup3e], 24.3.7.4).
- В C++ единицей инкапсуляции является класс – а не класс вместе с его ниже стоящей иерархией.
Второе утверждение не является тривиальным, поскольку, например, популярно следующее толкование защищенных (protected) членов ([Stroustrup3e], 15.3):
Если член защищен, его имя может использоваться только в функциях-членах и друзьях класса, в котором он объявлен, и классов, производных от него.
На самом деле данное утверждение является, говоря математическим языком, необходимым, но не достаточным:
Для использования имени защищенного члена необходимо, но не достаточно, чтобы обращение к имени производилось из функции-члена или друга класса, в котором он объявлен, или из классов, производных от него.
Утверждение становится необходимым и достаточным только при добавлении уточнения ([Stroustrup3e], 15.3.1):
Производный класс может осуществлять доступ к защищенным членам базового класса только для объектов его собственного типа.
Для демонстрации приведем код:
class Base {
protected:
void f() {}
private:
void g() {}
public:
void h() {
f(); //correct: *this object therefore object of own class Base
Base b;
b.f(); //correct: b is object of class Base (1)
}
};
class Derived : public Base {
void q() {
Derived d;
d.f(); //correct: d is object of class Derived therefore d->Derived::f() is called (2)
Base b;
b.f(); //compiler error: Base::f() is protected (3)
((Derived*)(&b))->f(); //dangerous non-dynamic downcast, but w/o compiler error (4)
}
};
Первое утверждение тривиально и демонстрируется (1): this->h() вызывает b.f(), вызов корректен, потому что хотя *this и b — разные объекты, но являются объектами одного класса Base.
Второе утверждение демонстрируется (2) и (3). Вызов (2) корректен, поскольку имя f унаследовано в классе Derived и this->q() вызывает d.Derived::f() — функцию класса Derived, а *this и d — разные объекты одного и того же класса Derived. Вызов (3) не корректен из-за попытки нарушения инкапсуляции: this->q() объекта *this класса Derived пытается вызвать защищенный Base::f() объекта b класса Base, то есть выйти за пределы своей единицы инкапсуляции — класса Derived в иерархию Base-Derived. this->q() имеет право вызывать только те защищенные члены, которые принадлежат объектам того же класса Derived, что и сам *this, например, Derived::f().
Строка (4) демонстрирует, что вторая часть утверждения о единице инкапсуляции обходится проще, чем первая: если для доступа к закрытым (private) членам из-за пределов единицы инкапсуляции приходится прибегать к трюкам типа низкоуровневого преобразования экземпляров объектов или переопределения ключевых слов, то для доступа к защищенным членам с нарушением инкапсуляции достаточно понижающего C-style cast указателей.
В заключении отметим, что на двойственность утверждения о единице инкапсуляции можно взглянуть с нескольких точек зрения:
- инкапсуляция соблюдается как для закрытых, так и для защищенных членов;
- единицы инкапсуляции не мельче и не крупнее класса;
- инкапсулируются имена и унаследованные имена, а не члены.
Литература
[Stroustrup3e] Б. Страуструп. Язык программирования C++, 3-е изд./ Пер. с англ. — СПб.; М.: «Невский диалект» — «Издательство БИНОМ», 1991 г.
Автор: oleg1977