|
Программирование >> Поддержка объектно-ориентированного программирования
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:
начальное связываем начальное связываем значение с массивом 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.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |