Бьерн Страуструп. Язык программирования С++



бет61/124
Дата16.07.2016
өлшемі3.27 Mb.
#204081
түріКнига
1   ...   57   58   59   60   61   62   63   64   ...   124

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

7.10 Инкремент и декремент


Если мы додумались до "хитрых указателей", то логично попробовать

переопределить операции инкремента ++ и декремента -- , чтобы

получить для классов те возможности, которые эти операции дают для

встроенных типов. Такая задача особенно естественна и необходима, если

ставится цель заменить тип обычных указателей на тип "хитрых указателей",

для которого семантика остается прежней, но появляются некоторые

действия динамического контроля. Пусть есть программа с распространенной

ошибкой:
void f1(T a) // традиционное использование

{

T v[200];



T* p = &v[10];

p--;


*p = a; // Приехали: `p' настроен вне массива,

// и это не обнаружено

++p;

*p = a; // нормально



}
Естественно желание заменить указатель p на объект класса

CheckedPtrToT, по которому косвенное обращение возможно только

при условии, что он действительно указывает на объект. Применять

инкремент и декремент к такому указателю будет можно только в том

случае, что указатель настроен на объект в границах массива и

в результате этих операций получится объект в границах того же

массива:
class CheckedPtrToT {

// ...


};
void f2(T a) // вариант с контролем

{

T v[200];



CheckedPtrToT p(&v[0],v,200);

p--;


*p = a; // динамическая ошибка:

// `p' вышел за границы массива

++p;

*p = a; // нормально



}
Инкремент и декремент являются единственными операциями в С++,

которые можно использовать как постфиксные и префиксные операции.

Следовательно, в определении класса CheckedPtrToT мы должны

предусмотреть отдельные функции для префиксных и постфиксных операций

инкремента и декремента:
class CheckedPtrToT {

T* p;


T* array;

int size;

public:

// начальное значение `p'



// связываем с массивом `a' размера `s'

CheckedPtrToT(T* p, T* a, int s);

// начальное значение `p'

// связываем с одиночным объектом

CheckedPtrToT(T* p);
T* operator++(); // префиксная

T* operator++(int); // постфиксная


T* operator--(); // префиксная

T* operator--(int); // постфиксная


T& operator*(); // префиксная

};
Параметр типа int служит указанием, что функция будет вызываться

для постфиксной операции. На самом деле этот параметр является

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

постфиксной и префиксной операции. Чтобы запомнить, какая версия

функции operator++ используется как префиксная операция, достаточно

помнить, что префиксной является версия без искусственного параметра,

что верно и для всех других унарных арифметических и логических

операций. Искусственный параметр используется только для "особых"

постфиксных операций ++ и --.

С помощью класса CheckedPtrToT пример можно записать так:
void f3(T a) // вариант с контролем

{

T v[200];



CheckedPtrToT p(&v[0],v,200);

p.operator--(1);

p.operator*() = a; // динамическая ошибка:

// `p' вышел за границы массива

p.operator++();

p.operator*() = a; // нормально

}
В упражнении $$7.14 [19] предлагается завершить определение класса

CheckedPtrToT, а другим упражнением ($$9.10[2]) является

преобразование его в шаблон типа, в котором для сообщений о

динамических ошибках используются особые ситуации. Примеры использования

операций ++ и -- для итераций можно найти в $$8.8.



Достарыңызбен бөлісу:
1   ...   57   58   59   60   61   62   63   64   ...   124




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет