0,0
рейтинг
20 февраля 2013 в 19:54

Разработка → В C++ единицей инкапсуляции является класс

C++*
Заголовок статьи на самом деле представляет собой не одно утверждение, а два, хотя оба они известны:
  1. В C++ единицей инкапсуляции является класс – а не отдельный объект ([Stroustrup3e], 24.3.7.4).
  2. В C++ единицей инкапсуляции является класс – а не класс вместе с его ниже стоящей иерархией.

Второе утверждение не является тривиальным, поскольку, например, популярно следующее толкование защищенных (protected) членов ([Stroustrup3e], 15.3):
Если член защищен, его имя может использоваться только в функциях-членах и друзьях класса, в котором он объявлен, и классов, производных от него.

На самом деле данное утверждение является, говоря математическим языком, необходимым, но не достаточным:
Для использования имени защищенного члена необходимо, но не достаточно, чтобы обращение к имени производилось из функции-члена или друга класса, в котором он объявлен, или из классов, производных от него.
Утверждение становится необходимым и достаточным только при добавлении уточнения ([Stroustrup3e], 15.3.1):
Производный класс может осуществлять доступ к защищенным членам базового класса только для объектов его собственного типа.

Для демонстрации приведем код:
class Base {
protected:
    void protected_f() {}
private:
    void private_f() {}
public:
    void base_f() {
        private_f(); //correct: *this object therefore object of own class Base
        Base b;
        b.private_f(); //correct: b is object of class Base (1)
    }
};

class Derived : public Base {
    void derived_f() {
        Derived d;
        d.protected_f(); //correct: d is object of class Derived therefore d->Derived::f() is called (2)
        Base b;
        b.protected_f(); //compiler error: Base::f() is protected (3)
        ((Derived*)(&b))->protected_f(); //dangerous non-dynamic downcast, but w/o compiler error (4)
    }
};

Первое утверждение тривиально и демонстрируется (1): this->base_f() вызывает b.private_f(), вызов корректен, потому что хотя *this и b — разные объекты, но являются объектами одного класса Base.
Второе утверждение демонстрируется (2) и (3). Вызов (2) корректен, поскольку имя protected_f унаследовано в классе Derived и this->derived_f() вызывает d.Derived::protected_f() — функцию класса Derived, а *this и d — разные объекты одного и того же класса Derived. Вызов (3) не корректен из-за попытки нарушения инкапсуляции: this->derived_f() объекта *this класса Derived пытается вызвать защищенный Base::protected_f() объекта b класса Base, то есть выйти за пределы своей единицы инкапсуляции — класса Derived в иерархию Base-Derived. this->derived_f() имеет право вызывать только те защищенные члены, которые принадлежат объектам того же класса Derived, что и сам *this, например, Derived::protected_f().
Строка (4) демонстрирует, что вторая часть утверждения о единице инкапсуляции обходится проще, чем первая: если для доступа к закрытым (private) членам из-за пределов единицы инкапсуляции приходится прибегать к трюкам типа низкоуровневого преобразования экземпляров объектов или переопределения ключевых слов, то для доступа к защищенным членам с нарушением инкапсуляции достаточно понижающего C-style cast указателей.

В заключении отметим, что на двойственность утверждения о единице инкапсуляции можно взглянуть с нескольких точек зрения:
  • инкапсуляция соблюдается как для закрытых, так и для защищенных членов;
  • единицы инкапсуляции не мельче и не крупнее класса;
  • инкапсулируются имена и унаследованные имена, а не члены.


Литература


[Stroustrup3e] Б. Страуструп. Язык программирования C++, 3-е изд./ Пер. с англ. — СПб.; М.: «Невский диалект» — «Издательство БИНОМ», 1991 г.
Олег Стрельников @oleg1977
карма
9,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (29)

  • +6
    Переименуйте методы, очень тяжело читать всякие f,g,h
    может стоит назвать protected_method, private_method, public_method или еще как.
    • 0
      Да, я исправлю, но никак не придумаю, как лучше это сделать:
      _method мне не нравится, потому что в C++ нет понятия метода
      Варианты:
      _member или _mem
      _function или _fun или _fn или _f
      просто _

      Пока никак не остановлюсь на чем-то. В каждом мне видится какой-то недостаток
      • 0
        Почему нет понятия? Функция, определённая в классе — чем не понятие? Вы хотели сказать, что нет строгого термина? Ну, понятия и не обязаны быть строгими.
  • 0
    Вопрос знатокам, что есть единицей инкапсуляции в Delphi? Если бы не то свойство, что приватные свойства класса можно дергать не только из экземпляров этого класса, но и из любого места модуля, в котором он определен, то можно было бы предположить, что единцей инкапсуляции является класс, но похоже что все-таки модуль, что странно, поскольку свойства и методы инапсулируются именно в класс.

    unit Unit2;
    
    interface
    
    type
      TSomeClass = Class
      private
        x : integer;
      end;
    
    var
      foo : TSomeClass;
    
    implementation
    
    initialization
      foo := TSomeClass.Create;
      foo.x := 10;
      foo.free;
    end.
    

    Delphi 7 спокойно компилирует этот код, и считает что в нем нет ошибок.
    • 0
      до появления strict private — модуль. в тех версиях где есть strict private — аналогично плюсам
      • 0
        Говоря о том, что было до strict private, есть 2 нюанса, которые говорят о том, что все-таки не модуль является единицей инкапсуляции:
        1. Приватный метод класса можно вызвать из другого модуля (например, посредством перегрузки (override) метода с другой областью видимости)
        2. Синтаксически и семантически свойства и методы инкапсулируются в класс.
        • 0
          есть 2 нюанса, которые говорят о том, что все-таки не модуль является единицей инкапсуляции:
          1. Приватный метод класса можно вызвать из другого модуля (например, посредством перегрузки (override) метода с другой областью видимости)

          это говорит только о том, что класс не инкапсулирует свои private члены. Модуль же инкапсулирует все, что находится под implementation. Помести туда класс и никто извне модуля не сможет к нему доступ иметь.

          2. Синтаксически и семантически свойства и методы инкапсулируются в класс.
          а синтаксис — это не важно. Важно только то, какая сущность способна ограничить доступ к своим членам извне. для младших дельфей — это модуль.
  • +9
    Как говорится… И чо?
    • +2
      Ваш вопрос «И чо?» задают новички C++ компилятору, когда последний пишет «Base::f() is protected» на строку (3), потому что новички думают о protected как о том, что написано в 15.3 у Строуструпа (см. пост)
      • 0
        Приведите пример, когда новичку в принципе может понадобиться так писать. Доступ к private/protected членам объекта того же типа дан был не для того.
        • 0
          Ну, например, я видел такой код, написанный опытным программистом на C, новичком C++:

          class Base {
           friend class Derived;
           protected:
           Base* Create();
          };
          
          class Derived : public Base {
          public:
           Base* CreateBase() {
            Base* b = new Derived;
            return b->Create();
           }
          };
          


          Собственно, исправляя этот код я и написал пост.

          Очевидно, что он просто ошибся в типе b: нужно было написать просто Derived* b = new Derived; (кстати,
          такая ошибка произошла тоже по понятным причинам, но это уже за рамками поста)

          Дальше он не понял, почему компилятор ругался на вызов b->Create() и попросту добавил Derived в друзья Base
          • +2
            Спагетти какое-то, ей-богу. Новичкам я бы вообще не разрешал использовать protected.
          • 0
            Пусть лучше дальше пишет на чистом Си. Вы его ещё спецификациями шаблонов напугайте и RTTI.
            • –2
              шаблоны и касты кроме C-style в этом проекте запрещены
              • 0
                А когда вы пишете на Си то вы заставляете разработчиков называть все переменные только идентификаторами с чётным числом символов? Откуда это необоснованное желание писать на каком-то вымышленном подмножестве языка?
                • 0
                  я никого не заставляю ни в этом проекте, ни в других. о данных конкретных запретах не высказываю никакой своей точки зрения. но вообще-то code style и projects restrictions — это обычная практика и тоже, фактически, приводит к формированию подмножеств языков. например, запрет на использование вещественных чисел может быть вполне обоснован для некоторых проектов. так что само по себе программирование на подмножестве — это не плохо.
                  • +4
                    Шаблоны запрещены — прощай стандартная библиотека? C++-style casts запрещены, так пусть у нас везде будет reinterpret_cast? Плохо когда ограничения не имеют смысла.

                    • 0
                      stl тоже запрещена. reinterpret_cast тоже запрещен. еще раз говорю, что это не мое решение и я его не комментирую
                      • +5
                        C-style cast сильнее чем reinterpret_cast, поэтому я и говорю что ограничение бессмысленное.
          • 0
            Я бы хотел уточнить. Пример кода не прямо скопирован из проекта, а воссоздан аналог, и в оригинале семантика не создания — Create(), а заполения класса Base из файла, при этом Derived — парсит файл, а Base — загружает в объект распарсеные значения. Так что если возникло ощущение, что очень сложно происходит создание, что код перенасыщен спагетти — то это уже моя вина: я написал первое пришедшее на ум имя функции Create(), потому что не думал об имени, а думал о том, какая функция какую вызывает. Здесь сразу несколько слоев ошибок. Самое главное, что между Base и Derived не должно быть наследования: должна быть композиция. Соответственно, нечего делать protected в классе Base. Но я хотел заострить внимание на проблемах более низкоуровневых, которые появляются если приведенную архитектуру считать данностью. Плохая архитектура — яма в которой человек сидит, у него три пути: выйти из ямы, остаться на месте и закопаться еще глубже. Первый путь не возможен по не зависящим от него причинам, скажем так, запрещен. Из двух оставшихся он выбирает последний, потому что по не знанию инструмента (языка), фактически делает движения вслепую. А если бы знал — мог бы остаться, не закапываясь глубже :)
  • +2
    Очередной теоретик программирования. Может уже программы будем писать эффективно, а не «правильно». Хотя топик создать — не мешки ворочать. Проще всего сослаться на некий авторитет после прочтения некой книги, куда проще чем создать годный продукт, который всем нужен.
    • +1
      одно другому не мешает. а иногда даже наоборот
      • –2
        Желание сделать всегда и везде код «правильно» гарантированно не помешает наваять архитектурного монстра, вместо того чтобы сделать всё просто и эффективно.
        • +1
          С опытом приходит понимание, что «эффективно» и «правильно» — это одно и то же.
          • –1
            Дело в том, что эффективно и правильно — это разные понятия, а «эффективно» и «правильно» — это по сути одинаково неэффективно и неправильно. Понимание правильного действительно приходит с опытом, и приводить в качестве аргумента книгу не есть хорошо, её даже понять прочитав можно сильно по-разному в зависимости от зрелости и понимания.
            Данный топик поможет вам осознать, что класс — единица инкапсуляции. Замечательно. Где профит?
  • +1
    > Строка (4) демонстрирует, что вторая часть утверждения о единице инкапсуляции обходится проще, чем первая

    Эта строка демонстрирует undefined behavior, а не то что вы написали.
    • –1
      То, что это UB никак не мешает тому, что приведенная конструкция проще, чем пример Герба Саттера в «Exceptional C++ Style» решении № 3 задачи 15. Там тоже UB.
  • –1
    > Строка (4) демонстрирует, что вторая часть утверждения о единице инкапсуляции обходится проще, чем первая

    Эта строка демонстрирует что автору надо руки оторвать. Лучше бы запретил даункасты вместо шаблонов, а если и делаешь даункаст то делай правильно, посредством динамик каста.
    • 0
      Это код не из проекта, а демонстрационный, приведен с той же целью, что упомянутый выше из «Exceptional C++ Style». Понятно, что так писать не нужно

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.