More Effective C++ | Item 6: Distinguish between prefix and postfix forms of increment and decrement operators Back to Item 5: Be wary of user-defined conversion functions Continue to Item 7: Never overload ||, or , Item 6: Distinguish between prefix and postfix forms of increment and decrement operators. Long, long ago (the late '80s) in a language far, far away (C++ at that time), there was no way to distinguish between prefix and postfix invocations of the ++ and -- operators. Programmers being programmers, they kvetched about this omission, and C++ was extended to allow overloading both forms of increment and decrement operators. There was a syntactic problem, however, and that was that overloaded functions are differentiated on the basis of the parameter types they take, but neither prefix nor postfix increment or decrement takes an argument. To surmount this linguistic pothole, it was decreed that postfix forms take an int argument, and compilers silently pass 0 as that int when those functions are called: class UPInt { // "unlimited precision int" public: UPInt& operator++(); // prefix ++ const UPInt operator++(int); // postfix ++ UPInt& operator--(); // prefix -- const UPInt operator--(int); // postfix -- UPInt& operator+=(int); // a += operator for UPInts // and ints ... }; UPInt i; ++i; // calls i.operator++(); i++; // calls i.operator++(0); --i; // calls i.operator--(); i--; // calls i.operator--(0); This convention is a little on the odd side, but you'll get used to it. More important to get used to, however, is this: the prefix and postfix forms of these operators return different types. In particular, prefix forms return a reference, postfix forms return a const object. We'll focus here on the prefix and postfix ++ operators, but the story for the -- operators is analogous. From your days as a C programmer, you may recall that the prefix form of the increment operator is sometimes called "increment and fetch," while the postfix form is often known as "fetch and increment." These two phrases are important to remember, because they all but act as formal specifications for how prefix and postfix increment should be implemented: // prefix form: increment and fetch UPInt& UPInt::operator++() { *this += 1; // increment return *this; // fetch } // postfix form: fetch and increment const UPInt UPInt::operator++(int) { UPInt oldValue = *this; // fetch ++(*this); // increment return oldValue; // return what was } // fetched Note how the postfix operator makes no use of its parameter. This is typical. The only purpose of the parameter is to distinguish prefix from postfix function invocation. Many compilers issue warnings (see Item E48) if you fail to use named parameters in the body of the function to which they apply, and this can be annoying. To avoid such warnings, a common strategy is to omit names for parameters you don't plan to use; that's what's been done above. It's clear why postfix increment must return an object (it's returning an old value), but why a const object? Imagine that it did not. Then the following would be legal: UPInt i; i++++; // apply postfix increment // twice This is the same as i.operator++(0).operator++(0); and it should be clear that the second invocation of operator++ is being applied to the object returned from the first invocation. There are two reasons to abhor this. First, it's inconsistent with the behavior of the built-in types. A good rule to follow when designing classes is when in doubt, do as the ints do, and the ints most certainly do not allow double application of postfix increment: int i; i++++; // error! The second reason is that double application of postfix increment almost never does what clients expect it to. As noted above, the second application of operator++ in a double increment changes the value of the object returned from the first invocation, not the value of the original object. Hence, if i++++; were legal, i would be incremented only once. This is counterintuitive and confusing (for both ints and UPInts), so it's best prohibited. C++ prohibits it for ints, but you must prohibit it yourself for classes you write. The easiest way to do this is to make the return type of postfix increment a const object. Then when compilers see i++++; // same as i.operator++(0).operator++(0); they recognize that the const object returned from the first call to operator++ is being used to call operator++ again. operator++, however, is a non-const member function, so const objects such as those returned from postfix operator++ can't call it.2 If you've ever wondered if it makes sense to have functions return const objects, now you know: sometimes it does, and postfix increment and decrement are examples. (For another example, turn to Item E21.) If you're the kind who worries about efficiency, you probably broke into a sweat when you first saw the postfix increment function. That function has to create a temporary object for its return value (see Item 19), and the implementation above also creates an explicit temporary object (oldValue) that has to be constructed and destructed. The prefix increment function has no such temporaries. This leads to the possibly startling conclusion that, for efficiency reasons alone, clients of UPInt should prefer prefix increment to postfix increment unless they really need the behavior of postfix increment. Let us be explicit about this. When dealing with user-defined types, prefix increment should be used whenever possible, because it's inherently more efficient. Let us make one more observation about the prefix and postfix increment operators. Except for their return values, they do the same thing: they increment a value. That is, they're supposed to do the same thing. How can you be sure the behavior of postfix increment is consistent with that of prefix increment? What guarantee do you have that their implementations won't diverge over time, possibly as a result of different programmers maintaining and enhancing them? Unless you've followed the design principle embodied by the code above, you have no such guarantee. That principle is that postfix increment and decrement should be implemented in terms of their prefix counterparts. You then need only maintain the prefix versions, because the postfix versions will automatically behave in a consistent fashion. As you can see, mastering prefix and postfix increment and decrement is easy. Once you know their proper return types and that the postfix operators should be implemented in terms of the prefix operators, there's very little more to learn. Back to Item 5: Be wary of user-defined conversion functions Continue to Item 7: Never overload ||, or , 2 Alas, it is not uncommon for compilers to fail to enforce this restriction. Before you write programs that rely on it, test your compilers to make sure they behave correctly. Return