C++ – pre-increment vs. post-increment

citerator

In the Google C++ Style Guide it says:

Preincrement and Predecrement

Use prefix form (++i) of the increment and decrement operators with iterators and other template objects.

When a variable is incremented (++i or i++) or decremented (--i or i--) and the value of the expression is not used, one must decide whether to preincrement (decrement) or postincrement (decrement).

When the return value is ignored, the "pre" form (++i) is never less efficient than the "post" form (i++), and is often more efficient. This is because post-increment (or decrement) requires a copy of i to be made, which is the value of the expression. If i is an iterator or other non-scalar type, copying i could be expensive. Since the two types of increment behave the same when the value is ignored, why not just always pre-increment?

The tradition developed, in C, of using post-increment when the expression value is not used, especially in for loops. Some find post-increment easier to read, since the "subject" (i) precedes the "verb" (++), just like in English.

For simple scalar (non-object) values there is no reason to prefer one form and we allow either. For iterators and other template types, use pre-increment.

I really must disagree with "When the return value is ignored, the "pre" form (++i) is never less efficient than the "post" form (i++), and is often more efficient. This is because post-increment (or decrement) requires a copy of i to be made, which is the value of the expression."

At least when it comes to pointers (and pointer registers can be used as integers).

Going back to my hardware days, there were plenty of machine instructions that had a form of addressing modes that had post-increment, post-decrement, and pre-decrement built in. The post-inc and post-dec modes cost 0 extra instruction cycles more than no increment at all. The incrementing happened after the instruction was executed while the next opcode was being fetched, but the pre-dec required an additional instruction cycle before the register was used. So, I don't really get it. Can someone explain google's case to me?

Best Answer

You may be right regarding pointers.

However:

  • C is not C++. C++ guarantees some very precise semantics especially around when copies are made, as this affects RAII: in C++, copies are an observable effect.

  • Not all iterators are pointers, but may be more complex objects. For example, output iterators like std::back_insert_iterator or iterators over non-contiguous data structures like std::unordered_map.

I really must disagree with "When the return value is ignored, the "pre" form (++i) is never less efficient than the "post" form (i++), and is often more efficient. This is because post-increment (or decrement) requires a copy of i to be made, which is the value of the expression."

at least when it comes to pointers.

There is nothing to disagree with here. The “++i is never less efficient than i++” just means that they may be equally efficient (which you argue for pointers). But if there is a difference, then ++i is expected to perform better.

To be precise, you argue that post-decrement may be more efficient on particular architectures that guarantee a more-efficient post-decrement than pre-decrement. However, such considerations only apply when you are developing for a particular microarchitecture. For portable code, the guarantees made by the language itself are more important. AFAIK neither x86 nor ARM instruction set families include a special post-increment instruction.

It is of course possible to create a counter-example where the pre-increment operator purposefully does unnecessary work, but for a consistent and minimal definition of post-increment and pre-increment, then post-increment can (and likely should) be implemented in terms of pre-increment:

struct X {
  ...
  // pre-increment
  X& operator++() { ...; return *this; }

  // post-increment
  X operator++(int) {
    X copy(*this);
    operator++();
    return copy;
  }
};

Since post-increment must return the state of the object before the increment was executed, a copy of the state prior to the increment is unavoidable for user-defined types.

Of course a sufficiently smart compiler may be able to inline the post-increment and avoid the copy (assuming a trivial copy ctor) by exploiting the sequencing rules and the as-if rule, but at best we are back to “equally efficient”.

Related Topic