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



7.9 Косвенное обращение

Операцию косвенного обращения к члену -> можно определить как унарную
постфиксную операцию. Это значит, если есть класс

           class Ptr {
             // ...
             X* operator->();
           };

объекты класса Ptr могут использоваться для доступа к членам класса
X также, как для этой цели используются указатели:

          void f(Ptr p)
          {
            p->m = 7;  // (p.operator->())->m = 7
          }

Превращение объекта p в указатель p.operator->() никак не зависит от
члена m, на который он указывает. Именно по этой причине operator->()
является унарной постфиксной операцией. Однако, мы не вводим новых
синтаксических обозначений, так что имя члена по-прежнему должно
идти после -> :

           void g(Ptr p)
           {
              X* q1 = p->;  // синтаксическая ошибка
              X* q2 = p.operator->(); // нормально
           }

     Перегрузка операции -> прежде всего используется для создания
"хитрых указателей", т.е. объектов, которые помимо использования как
указатели позволяют проводить некоторые операции при каждом обращении
к указуемому объекту с их помощью. Например, можно определить класс
RecPtr для организации доступа к объектам класса Rec, хранимым на
диске. Параметром конструктора RecPtr является имя, которое будет
использоваться для поиска объекта на диске. При обращении к объекту
с помощью функции RecPtr::operator->() он переписывается в основную
память, а в конце работы деструктор RecPtr записывает измененный
объект обратно на диск.

            class RecPtr {
               Rec* in_core_address;
               const char* identifier;
               // ...
            public:
               RecPtr(const char* p)
               : identifier(p) { in_core_address = 0; }
               ~RecPtr()
                  { write_to_disc(in_core_address,identifier); }
               Rec* operator->();
            };

            Rec* RecPtr::operator->()
            {
              if (in_core_address == 0)
                  in_core_address = read_from_disc(identifier);
              return in_core_address;
            }

Использовать это можно так:

            main(int argc, const char* argv)
            {
              for (int i = argc; i; i--) {
                  RecPtr p(argv[i]);
                  p->update();
              }
            }

На самом деле, тип RecPtr должен определяться как шаблон типа
(см. $$8), а тип структуры Record будет его параметром. Кроме
того, настоящая программа будет содержать обработку ошибок и
взаимодействие с диском будет организовано не столь примитивно.
      Для обычных указателей операция -> эквивалентна операциям,
использующим * и []. Так, если описано

            Y* p;

то выполняется соотношение

            p->m == (*p).m == p[0].m

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

            class X {
              Y* p;
            public:
              Y* operator->() { return p; }
              Y& operator*() { return *p; }
              Y& operator[](int i) { return p[i]; }
            };

    Если в вашем классе определено более одной подобной операции,
разумно будет обеспечить эквивалентность, точно так же, как разумно
предусмотреть для простой переменной x некоторого класса, в котором
есть операции ++, += = и +, чтобы операции ++x и x+=1 были
эквивалентны x=x+1.
   Перегрузка -> как и перегрузка [] может играть важную роль для
целого класса настоящих программ, а не является просто экспериментом
ради любопытства. Дело в том, что в программировании понятие
косвенности является ключевым, а перегрузка -> дает ясный, прямой
и эффективный способ представления этого понятия в программе.
Есть другая точка зрения на операцию ->, как на средство задать
в С++ ограниченный, но полезный вариант понятия делегирования
(см. $$12.2.8 и 13.9).

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