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…