Skip to content

14.6. Increment and Decrement Operators

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.

TIP

Best Practices

Classes that define increment or decrement operators should define both the prefix and postfix versions. These operators usually should be defined as members.

Defining Prefix Increment/Decrement Operators

To illustrate the increment and decrement operators, we’ll define these operators for our StrBlobPtr class (§ 12.1.6, p. 474):

c++
class StrBlobPtr {
public:
    // increment and decrement
    StrBlobPtr& operator++();       // prefix operators
    StrBlobPtr& operator--();
    // other members as before
};

TIP

Best Practices

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:

c++
// 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).

Differentiating Prefix and Postfix Operators

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:

c++
class StrBlobPtr {
public:
    // increment and decrement
    StrBlobPtr operator++(int);    // postfix operators
    StrBlobPtr operator--(int);
    // other members as before
};

TIP

Best Practices

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:

c++
// 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

c++
++*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.

INFO

The int parameter is not used, so we do not give it a name.

Calling the Postfix Operators Explicitly

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:

c++
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.

INFO

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?