C++. Сборник рецептов.

8.13. Перегрузка операторов инкремента и декремента.

Проблема.

Имеется класс, для которого имеют смысл операции инкремента и декремента, и требуется перегрузить operator++ и operator--, которые позволят легко и интуитивно выполнять инкремент и декремент объектов этого класса.

Решение.

Чтобы это сделать, перегрузите префиксную и постфиксную формы ++ и --. Пример 8.14 показывает обычную методику перегрузки операторов инкремента и декремента.

Пример 8.14. Перегрузка инкремента и декремента.

#include <iostream>

Using namespace std;

Class Score {

Public:

 Score() : score_(0) {}.

 Score(int i) : score_(i) {}.

 Score& operator++() {

  // префикс.

  ++score_;

  return(*this);

 }

 const Score operator++(int) {

  // постфикс.

  Score tmp(*this);

  ++(*this); // Использование префиксного оператора.

  return(tmp);

 }

 Score& operator--() {

  --score_;

  return(*this);

 }

 const Score operator--(int x) {

  Score tmp(*this);

  --(*this);

  return(tmp);

 }

 int getScore() const {return(score_);}.

Private:

 int score_;

};

Int main() {

 Score player1(50);

 player1++;

 ++player1; // score = 52.

 cout << "Счет = " << player1.getScore() << '\n';

 (--player1)--; // score_ = 50.

 cout << "Счет = " << player1.getScore() << '\n';

}

Обсуждение.

Операторы инкремента и декремента часто имеют смысл для классов, которые представляют некоторые разновидности целых значений. Если вы понимаете разницу между префиксной и постфиксной формами и следуете соглашениям о возвращаемых значениях, то их легко использовать.

Представьте себе инкремент целого числа. С помощью оператора ++ имеется два способа выполнить его для некоторого целого i.

I++; // постфиксный.

++i; // префиксный.

Оба инкрементируют i: первая версия создает временную копию i, инкрементирует i и затем возвращает временное значение, а вторая инкрементирует i и затем возвращает его. C++ позволяет выполнять перегрузку операторов, что означает, что вы можете заставить свой собственный тип (класс или enum) вести себя так же, как и int.

Чтобы добиться нужного эффекта, перегрузите operator++ и operator--. Пример 8.14 иллюстрирует, как перегружать префиксную и постфиксную версии.

Score& operator++() { // префиксный.

 ++score_;

 return(*this);

}

Const Score operator++(int) { // постфиксный.

 Score tmp(*this);

 ++(*this);

 return(tmp);

}

Префикс выглядит так, как и следует ожидать, но компилятор различает эти две версии, и в объявление постфиксной версии включается параметр int. Он не имеет семантического применения — он всегда передается как ноль, так что его можно игнорировать.

После этого класс Score можно использовать как int.

Score player1(50);

Player1++;

++player1; // score_ = 52.

Вы, вероятно, заметили, что сигнатуры префиксной версии operator++ возвращают ссылку на текущий класс. Именно так и следует делать (а не возвращать, к примеру, void), чтобы инкрементируемый или декрементируемый объект мог использоваться в других выражениях. Рассмотрим такую строку из примера.

(--player1)--;

Да, это странно, но она иллюстрирует этот момент. Если бы префиксный operator-- не возвращал чего-то осмысленного, то это выражение не скомпилировалось бы. Еще один пример показывает вызов функции.

Foo(--player1);

Функция foo ожидает аргумент типа Score, и для корректной компиляции именно это должно возвращаться из префиксного operator--.

Перегрузка операторов — это мощная возможность, которая позволяет для типов, определяемых пользователем, использовать те же операторы, что и для встроенных типов. Сторонники других языков, которые не поддерживают перегрузку операторов, утверждают, что эта возможность сбивает с толку и очень сложна, и, следует признать, может быть перегружено очень много операторов, соответствующих любому поведению. Но когда дело касается простого инкремента и декремента, хорошо иметь возможность изменить поведение класса так, как этого хочется.

Смотри также.

Рецепт 8.14.