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

в 15:54, , рубрики: c++, private, protected, инкапсуляция, класс, объект, метки: , , , , ,

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

  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 г.

Автор: oleg1977

Источник

Поделиться

* - обязательные к заполнению поля