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



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.

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