Программирование >>  Поддержка объектно-ориентированного программирования 

1 ... 59 60 61 [ 62 ] 63 64 65 ... 120


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)

Функция operator() должна быть функцией-членом.

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 .

Перегрузка -> как и перегрузка [] может играть важную роль для целого класса настоящих программ, а не является просто экспериментом ради любопытства. Дело в том, что в программировании понятие косвенности является ключевым, а перегрузка -> дает ясный, прямой эффективный способ представления этого понятия в программе. Есть другая точка зрения на операцию ->, как на средство задать в С++ ограниченный, но полезный вариант понятия делегирования (см. $$1 2.2.8 и 1 3.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:

CheckedPtrToT(T* p, T* a.

int s);

CheckedPtrToT(T* p);

T* operator++();

T* operator++(int);

T* operator--();

T* operator--(int);

T& operator*();

начальное связываем

начальное связываем

значение с массивом

a размера s

значение p

с одиночным объектом

префиксная

постфиксная

префиксная

постфиксная

префиксная

Параметр типа int служит указанием, что функция будет вызываться для постфиксной операции. На самом деле этот параметр является искусственным и никогда не используется, а служит только для различия постфиксной и префиксной операции. Чтобы запомнить, какая версия функции operator++ используется как префиксная операция, достаточно помнить, что префиксной является версия без искусственного параметра, что верно и для всех других унарных арифметических и логических операций. Искусственный параметр используется только для особых постфиксных операций ++ и -- . С помощью класса CheckedPtrToT пример можно записать так:

void f3(T a) вариант с контролем

T v[200];

CheckedPtrToT p(&v[0],v,200); p.operator--(1); p.operator*() = a;

p.operator++(); p.operator*() = a;

динамическая ошибка:

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

нормально

В упражнении $$7.1 4 [1 9] предлагается завершить определение класса CheckedPtrToT, а другим упражнением ($$9.1 0[2]) является преобразование его в шаблон типа, в котором для сообщений о динамических ошибках используются особые ситуации. Примеры использования операций ++ и -- для итераций можно найти в $$8.8.



1 ... 59 60 61 [ 62 ] 63 64 65 ... 120

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика