В C++ единицей инкапсуляции является класс

от автора

Заголовок статьи на самом деле представляет собой не одно утверждение, а два, хотя оба они известны:

  1. В C++ единицей инкапсуляции является класс – а не отдельный объект ([Stroustrup3e], 24.3.7.4).
  2. В 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 г.

ссылка на оригинал статьи http://habrahabr.ru/post/170127/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *