Опубликован: 10.10.2006 | Доступ: свободный | Студентов: 6086 / 462 | Оценка: 4.26 / 3.88 | Длительность: 31:30:00
Лекция 7:

Перегрузка операций

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 должен определяться как шаблон типа (см. \S 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.

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

Дарья Федотова
Дарья Федотова
Сергей Березовский
Сергей Березовский

В рамках проф. переподготовки по программе "Программирование"

Есть курсы, которые я уже прошел. Но войдя в курс я вижу, что они не зачтены (Язык Ассемблера и архитектура ЭВМ, Программирование на С++ для профессионалов). Это как?

Роман Островский
Роман Островский
Украина
Оксана Пагина
Оксана Пагина
Россия, Москва