Operator++(int): A case for post-incrementing

If you love writing efficient code, you probably cringe when you see code like this, using post-increment for no gain:

for ( int i = 0; i < max_value; i++ )
{ // do stuff 
}

instead of :

for ( int i = 0; i < max_value; ++i )
{ // do stuff 
}

There are reasons to prefer one over the other. First, let’s start with the signature.

T& T::operator++(); // pre-increment

// vs 

T T::operator++(int); / post-increment

Other than the unused int parameter used to differentiate between the function signatures, the important part is the return type. In the pre-increment the return type is a reference. Usually to *this. In the post-increment, we have a T (not a reference) which indicates we are returning a copy and a possible inefficiency.

class T
{
private: 
	int my_value;

public:
	T& T::operator++()
	{
		// increment T.
		// possibly as such:
		my_value += 1;
		return *this;
	}

Whereas post-increment could be implemented as such:

T T::operator++(int)
{
    // make a temp copy to hold the current value
    // to return;
    T temp(*this);

    // Increment using the pre-increment we defined.
    ++temp;

    // return the original value.
    return temp;
}

The main problem with post-increment is the requirement to return the original value, meaning it must be saved somewhere while the value is incremented. This usually means a copy-constructor.

But to come back to builtin types, it becomes more of a habit. In the case of our for loop above, without any side-effects, i++ may well be optimized to ++i or the loop unrolled. But, all things being equal, it’s best to use the form which doesn’t count on optimization.

There are however some interesting cases where post-increment’s return of a temp value is useful. Cases where passing an argument to a function which can make the variable passed as an argument invalid. In such a case, using post-increment is very useful since the variable in the algorithm stays valid for future use:


std::list<int> my_list = { 1, 2, 3, 4, 5 };
std::list<int>::iterator iter = my_list.begin();

// do something.
++iter; 
// do something else and move iter along...

// Now erase what is pointed at by iter.
my_list.erase(iter++);
	
// iter is still valid.

// Now replace previous value with 42. 
my_list.insert(iter, 42);

However, the following invalidates the iterator:

// DON'T do this
my_list.erase(iter);
// Ooops, can't use iter anymore. It is now invalid.

// ERROR: Attempting to use an invalid iterator.
my_list.insert(iter, 42);

An alternative (and for compleness here) is to use the return value of erase() which returns a valid iterator to the element past the one we just erased. Does away completely with the need for incrementing, but you get the idea.

// Alternatively, can do:
iter = my_list.erase(iter);

// Can safely use the iterator.
my_list.insert(iter, 42);

There you have it, one more (ok, still few) case for using post-increment. Know any other clever uses of post-increment? Let me know…

Leave a Reply

Your email address will not be published. Required fields are marked *