Бьерн Страуструп - Язык программирования С++. Главы 5-8
Страница 5. Ссылка на себя



5.2.3 Ссылка на себя

В функции-члене можно непосредственно использовать имена членов
того объекта, для которого она была вызвана:

        class X {
          int m;
        public:
          int readm() { return m; }
        };

        void f(X aa, X bb)
        {
          int a = aa.readm();
          int b = bb.readm();
          // ...
        }

При первом вызове readm() m обозначает aa.m, а при втором - bb.m.
    У функции-члена есть дополнительный скрытый параметр, являющийся
указателем на объект, для которого вызывалась функция. Можно явно
использовать этот скрытый параметр под именем this. Считается, что
в каждой функции-члене класса X указатель this описан неявно как

        X *const this;

и инициализируется, чтобы указывать на объект, для которого
функция-член вызывалась. Этот указатель нельзя изменять, поскольку
он постоянный (*const). Явно описать его тоже нельзя, т.к. this -
это служебное слово. Можно дать эквивалентное описание класса X:

       class X {
          int m;
       public:
          int readm() { return this->m; }
       };

Для обращения к членам использовать this излишне. В основном this
используется в функциях-членах, непосредственно работающих с
указателями. Типичный пример - функция, которая вставляет элемент
в список с двойной связью:

         class dlink {
            dlink* pre;  // указатель на предыдущий элемент
            dlink* suc;  // указатель на следующий элемент
         public:
            void append(dlink*);
            // ...
         };

         void dlink::append(dlink* p)
         {
           p->suc = suc;    // т.е. p->suc = this->suc
           p->pre = this;   // явное использование "this"
           suc->pre = p;    // т.е. this->suc->pre = p
           suc = p;         // т.е. this->suc = p
         }

         dlink* list_head;

         void f(dlink* a, dlink* b)
         {
           // ...
           list_head->append(a);
           list_head->append(b);
         }

 Списки с такой общей структурой служат фундаментом списочных классов,
 описываемых в главе 8. Чтобы присоединить звено к списку, нужно
 изменить объекты, на которые настроены указатели this, pre и suc.
 Все они имеют тип dlink, поэтому функция-член dlink::append() имеет
 к ним доступ. Защищаемой единицей в С++ является класс, а не отдельный
 объект класса.
    Можно описать функцию-член таким образом, что объект, для которого
 она вызывается, будет доступен ей только по чтению. Тот факт, что
 функция не будет изменять объект, для которого она вызывается
 (т.е. this*), обозначается служебным словом const в конце списка
 параметров:

            class X {
               int m;
            public:
               readme() const { return m; }
               writeme(int i) { m = i; }
            };

  Функцию-член со спецификацией const можно вызывать для постоянных
  объектов, а функцию-член без такой спецификации - нельзя:

            void f(X& mutable, const X& constant)
            {
              mutable.readme();    // нормально
              mutable.writeme(7);  // нормально
              constant.readme();   // нормально
              constant.writeme(7); // ошибка
            }

   В этом примере разумный транслятор смог бы обнаружить, что
   функция X::writeme() пытается изменить постоянный объект. Однако,
   это непростая задача для транслятора. Из-за раздельной
   трансляции он в общем случае не может гарантировать "постоянство"
   объекта, если нет соответствующего описания со спецификацией
   const. Например, определения readme() и writeme() могли быть в
   другом файле:

            class X {
               int m;
            public:
               readme() const;
               writeme(int i);
            };

  В таком случае описание readme() со спецификацией const существенно.
      Тип указателя this в постоянной функции-члене класса X есть
  const X *const. Это значит, что без явного приведения с помощью this
  нельзя изменить значение объекта:

            class X {
               int m;
            public:
               // ...
               void implicit_cheat() const { m++; }  // ошибка
               void explicit_cheat() const { ((X*)this)->m++; }
                    // нормально
           };

  Отбросить спецификацию const можно потому, что понятие
  "постоянства" объекта имеет два значения. Первое, называемое
  "физическим постоянством" состоит в том, что объект хранится
  в защищенной от записи памяти. Второе, называемое "логическим
  постоянством" заключается в том, что объект выступает как
  постоянный (неизменяемый) по отношению к пользователям. Операция
  над логически постоянным объектом может изменить часть данных
  объекта, если при этом не нарушается его постоянство
  с точки зрения пользователя. Операциями, ненарушающими логическое
  постоянство объекта, могут быть буферизация значений, ведение
  статистики, изменение переменных-счетчиков в постоянных
  функциях-членах.
       Логического постоянства можно достигнуть приведением, удаляющим
  спецификацию const:

           class calculator1 {
              int cache_val;
              int cache_arg;
              // ...
           public:
              int compute(int i) const;
              // ...
           };

           int calculator1::compute(int i) const
           {
             if (i == cache_arg) return cache_val;
             // нелучший способ
             ((calculator1*)this)->cache_arg = i;
             ((calculator1*)this)->cache_val = val;
             return val;
           }

  Этого же результата можно достичь, используя указатель на данные
  без const:

           struct cache {
               int val;
               int arg;
           };

           class calculator2 {
               cache* p;
               // ...
           public:
               int compute(int i) const;
               // ...
           };

           int calculator2::compute(int i) const
           {
             if (i == p->arg) return p->val;
             // нелучший способ
             p->arg = i;
             p->val = val;
             return val;
           }

 Отметим, что const нужно указывать как в описании, так и в определении
 постоянной функции-члена. Физическое постоянство обеспечивается
 помещением объекта в защищенную по записи память, только если в классе
 нет конструктора ($$7.1.6).

 
« Предыдущая статья   Следующая статья »