The increment (++
) and decrement (--
) operators are most often implemented for iterator classes. These operators let the class move between the elements of a sequence. There is no language requirement that these operators be members of the class. However, because these operators change the state of the object on which they operate, our preference is to make them members.
For the built-in types, there are both prefix and postfix versions of the increment and decrement operators. Not surprisingly, we can define both the prefix and postfix instances of these operators for our own classes as well. We’ll look at the prefix versions first and then implement the postfix ones.
Classes that define increment or decrement operators should define both the prefix and postfix versions. These operators usually should be defined as members.
To illustrate the increment and decrement operators, we’ll define these operators for our StrBlobPtr
class (§ 12.1.6, p. 474):
class StrBlobPtr {
public:
// increment and decrement
StrBlobPtr& operator++(); // prefix operators
StrBlobPtr& operator--();
// other members as before
};
To be consistent with the built-in operators, the prefix operators should return a reference to the incremented or decremented object.
The increment and decrement operators work similarly to each other—they call check
to verify that the StrBlobPtr
is still valid. If so, check
also verifies that its given index is valid. If check
doesn’t throw an exception, these operators return a reference to this object.
In the case of increment, we pass the current value of curr
to check
. So long as that value is less than the size of the underlying vector
, check
will return. If curr
is already at the end of the vector
, check
will throw:
// prefix: return a reference to the incremented/decremented object
StrBlobPtr& StrBlobPtr::operator++()
{
// if curr already points past the end of the container, can't increment it
check(curr, "increment past end of StrBlobPtr");
++curr; // advance the current state
return *this;
}
StrBlobPtr& StrBlobPtr::operator--()
{
// if curr is zero, decrementing it will yield an invalid subscript
--curr; // move the current state back one element
check(-1, "decrement past begin of StrBlobPtr");
return *this;
}
The decrement operator decrements curr
before calling check
. That way, if curr
(which is an unsigned
number) is already zero, the value that we pass to check
will be a large positive value representing an invalid subscript (§ 2.1.2, p. 36).
There is one problem with defining both the prefix and postfix operators: Normal overloading cannot distinguish between these operators. The prefix and postfix versions use the same symbol, meaning that the overloaded versions of these operators have the same name. They also have the same number and type of operands.
To solve this problem, the postfix versions take an extra (unused) parameter of type int
. When we use a postfix operator, the compiler supplies 0 as the argument for this parameter. Although the postfix function can use this extra parameter, it usually should not. That parameter is not needed for the work normally performed by a postfix operator. Its sole purpose is to distinguish a postfix function from the prefix version.
We can now add the postfix operators to StrBlobPtr
:
class StrBlobPtr {
public:
// increment and decrement
StrBlobPtr operator++(int); // postfix operators
StrBlobPtr operator--(int);
// other members as before
};
To be consistent with the built-in operators, the postfix operators should return the old (unincremented or undecremented) value. That value is returned as a value, not a reference.
The postfix versions have to remember the current state of the object before incrementing the object:
// postfix: increment/decrement the object but return the unchanged value
StrBlobPtr StrBlobPtr::operator++(int)
{
// no check needed here; the call to prefix increment will do the check
StrBlobPtr ret = *this; // save the current value
++*this; // advance one element; prefix ++ checks the increment
return ret; // return the saved state
}
StrBlobPtr StrBlobPtr::operator--(int)
{
// no check needed here; the call to prefix decrement will do the check
StrBlobPtr ret = *this; // save the current value
--*this; // move backward one element; prefix -- checks the decrement
return ret; // return the saved state
}
Each of our operators calls its own prefix version to do the actual work. For example, the postfix increment operator executes
++*this
This expression calls the prefix increment operator. That operator checks that the increment is safe and either throws an exception or increments curr
. Assuming check
doesn’t throw an exception, the postfix functions return the stored copy in ret
. Thus, after the return, the object itself has been advanced, but the value returned reflects the original, unincremented value.
As we saw on page 553, we can explicitly call an overloaded operator as an alternative to using it as an operator in an expression. If we want to call the postfix version using a function call, then we must pass a value for the integer argument:
StrBlobPtr p(a1); // p points to the vector inside a1
p.operator++(0); // call postfix operator++
p.operator++(); // call prefix operator++
The value passed usually is ignored but is necessary in order to tell the compiler to use the postfix version.
Exercises Section 14.6
Exercise 14.27: Add increment and decrement operators to your
StrBlobPtr
class.Exercise 14.28: Define addition and subtraction for
StrBlobPtr
so that these operators implement pointer arithmetic (§ 3.5.3, p. 119).Exercise 14.29: We did not define a
const
version of the increment and decrement operators. Why not?