Overloading the increment (++
) and decrement (--
) operators are pretty straightforward, with one small exception. There are actually two versions of the increment and decrement operators: a prefix increment and decrement (e.g. ++x; --y;
) and a postfix increment and decrement (e.g. x++; y--;
).
Because the increment and decrement operators are both unary operators and they modify their operands, they’re best overloaded as member functions. We’ll tackle the prefix versions first because they’re the most straightforward.
Overloading prefix increment and decrement
Prefix increment and decrement is overloaded exactly the same as any normal unary operator. We’ll do this one by example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
#include <iostream> class Digit { private: int m_digit; public: Digit(int digit=0) : m_digit(digit) { } Digit& operator++(); Digit& operator--(); friend std::ostream& operator<< (std::ostream &out, const Digit &d); }; Digit& Digit::operator++() { // If our number is already at 9, wrap around to 0 if (m_digit == 9) m_digit = 0; // otherwise just increment to next number else ++m_digit; return *this; } Digit& Digit::operator--() { // If our number is already at 0, wrap around to 9 if (m_digit == 0) m_digit = 9; // otherwise just decrement to next number else --m_digit; return *this; } std::ostream& operator<< (std::ostream &out, const Digit &d) { out << d.m_digit; return out; } int main() { Digit digit(8); std::cout << digit; std::cout << ++digit; std::cout << ++digit; std::cout << --digit; std::cout << --digit; return 0; } |
Our Digit class holds a number between 0 and 9. We’ve overloaded increment and decrement so they increment/decrement the digit, wrapping around if the digit is incremented/decremented out range.
This example prints:
89098
Note that we return *this. The overloaded increment and decrement operators return the current implicit object so multiple operators can be “chained” together.
Overloading postfix increment and decrement
Normally, functions can be overloaded when they have the same name but a different number and/or different type of parameters. However, consider the case of the prefix and postfix increment and decrement operators. Both have the same name (eg. operator++), are unary, and take one parameter of the same type. So how it is possible to differentiate the two when overloading?
The answer is that C++ uses a “dummy variable” or “dummy argument” for the postfix operators. This argument is a fake integer parameter that only serves to distinguish the postfix version of increment/decrement from the prefix version. Here is the above Digit class with both prefix and postfix overloads:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
class Digit { private: int m_digit; public: Digit(int digit=0) : m_digit(digit) { } Digit& operator++(); // prefix Digit& operator--(); // prefix Digit operator++(int); // postfix Digit operator--(int); // postfix friend std::ostream& operator<< (std::ostream &out, const Digit &d); }; Digit& Digit::operator++() { // If our number is already at 9, wrap around to 0 if (m_digit == 9) m_digit = 0; // otherwise just increment to next number else ++m_digit; return *this; } Digit& Digit::operator--() { // If our number is already at 0, wrap around to 9 if (m_digit == 0) m_digit = 9; // otherwise just decrement to next number else --m_digit; return *this; } Digit Digit::operator++(int) { // Create a temporary variable with our current digit Digit temp(m_digit); // Use prefix operator to increment this digit ++(*this); // apply operator // return temporary result return temp; // return saved state } Digit Digit::operator--(int) { // Create a temporary variable with our current digit Digit temp(m_digit); // Use prefix operator to decrement this digit --(*this); // apply operator // return temporary result return temp; // return saved state } std::ostream& operator<< (std::ostream &out, const Digit &d) { out << d.m_digit; return out; } int main() { Digit digit(5); std::cout << digit; std::cout << ++digit; // calls Digit::operator++(); std::cout << digit++; // calls Digit::operator++(int); std::cout << digit; std::cout << --digit; // calls Digit::operator--(); std::cout << digit--; // calls Digit::operator--(int); std::cout << digit; return 0; } |
This prints
5667665
There are a few interesting things going on here. First, note that we’ve distinguished the prefix from the postfix operators by providing an integer dummy parameter on the postfix version. Second, because the dummy parameter is not used in the function implementation, we have not even given it a name. This tells the compiler to treat this variable as a placeholder, which means it won’t warn us that we declared a variable but never used it.
Third, note that the prefix and postfix operators do the same job -- they both increment or decrement the object. The difference between the two is in the value they return. The overloaded prefix operators return the object after it has been incremented or decremented. Consequently, overloading these is fairly straightforward. We simply increment or decrement our member variables, and then return *this.
The postfix operators, on the other hand, need to return the state of the object before it is incremented or decremented. This leads to a bit of a conundrum -- if we increment or decrement the object, we won’t be able to return the state of the object before it was incremented or decremented. On the other hand, if we return the state of the object before we increment or decrement it, the increment or decrement will never be called.
The typical way this problem is solved is to use a temporary variable that holds the value of the object before it is incremented or decremented. Then the object itself can be incremented or decremented. And finally, the temporary variable is returned to the caller. In this way, the caller receives a copy of the object before it was incremented or decremented, but the object itself is incremented or decremented. Note that this means the return value of the overloaded operator must be a non-reference, because we can’t return a reference to a local variable that will be destroyed when the function exits. Also note that this means the postfix operators are typically less efficient than the prefix operators because of the added overhead of instantiating a temporary variable and returning by value instead of reference.
Finally, note that we’ve written the post-increment and post-decrement in such a way that it calls the pre-increment and pre-decrement to do most of the work. This cuts down on duplicate code, and makes our class easier to modify in the future.
![]() |
![]() |
![]() |
we could use anonymous return ?
Hello,
Is the following a bad-written code?I got the same result as yours.
>>On the other hand, if we return the state of the object before we increment or decrement it, the increment or decrement will never be called.
Did you mean my 'return' code part in the code above?
>>Also note that this means the postfix operators are typically less efficient than the prefix operators because of the added overhead of instantiating a temporary variable and returning by value instead of reference.
Is this how such operators are implemented in the C++ language itself?Now it makes sense why you asked us to use ++x instead of x++ :)
If `operator++` only increases `m_digit`, your solution is fine too. If `operator++` did a lot of other things as well, you'd have duplicate code in the ++prefix and ++postfix overloads. By calling ++prefix from postfix++, we're guaranteed that they behave the same (Only the return value is different).
I'm just as confused as you about the sentence about incrementing after returning. Maybe Alex means
Fundamental types (`int`, etc.) don't have implementations. The compiler just knows how operations on these types work. But yes, ++ and -- work the same for these types. If you use postfix++, the variable has to be copied. However, for fundamental types, the compiler will most likely change postfix++ to ++prefix if it notices that you don't use the return value of postfix++.
Thank you so much dear nascardriver. <3 <3
So I can't get the idea why do we need to create a temporary variable
"Digit temp(m_digit)" if we can do this:
I've compared these two methods using timing from lesson 8.16 and the one without a temporary variable is much quicker. Check this out please.
Am I missing something?
I agree. Even more, you could do
because m_digit is an int, so the default operator works fine there.
The only reason why I think the example they gave is correct is because, in this case, they are not only increasing and decreasing, but also checking if the digit is over 9 or less than 0, so neither your solution nor mine would do the full work.
isn't that returning an INT type ? m_digit is INT but your function return type is of type Digit1 class.
how did that work ?
oh i get it now thanks.
So I can't get the idea why do we need to create a temporary variable
"Digit temp(m_digit)" if we can do:
I've compared these two methods using timing from lesson 8.16 and the one without a temporary variable is much quicker:
#include <iostream>
#include <chrono> // for std::chrono functions
class Timer
{
private:
using clock_t = std::chrono::high_resolution_clock;
using second_t = std::chrono::duration<double, std::ratio<1> >;
std::chrono::time_point<clock_t> m_beg;
public:
Timer() : m_beg(clock_t::now())
{
}
void reset()
{
m_beg = clock_t::now();
}
double elapsed() const
{
return std::chrono::duration_cast<second_t>(clock_t::now() - m_beg).count();
}
};
class Digit1
{
private:
public:
int m_digit;
Digit1(int digit = 0)
: m_digit(digit) {}
Digit1 operator--(int); // postfix
friend std::ostream& operator<< (std::ostream& out, const Digit1& d);
};
class Digit2
{
private:
public:
int m_digit;
Digit2(int digit = 0)
: m_digit(digit) {}
Digit2& operator--(); // prefix
Digit2 operator--(int); // postfix
friend std::ostream& operator<< (std::ostream& out, const Digit2& d);
};
Digit2& Digit2::operator--()
{
--m_digit;
return *this;
}
Digit2 Digit2::operator--(int)
{
Digit2 temp(m_digit);
--(*this);
return temp;
}
Digit1 Digit1::operator--(int)
{
return Digit1((--m_digit) + 1); // return saved state
}
std::ostream& operator<< (std::ostream& out, const Digit1& d)
{
out << d.m_digit;
return out;
}
std::ostream& operator<< (std::ostream& out, const Digit2& d)
{
out << d.m_digit;
return out;
}
int main()
{
Timer t;
Digit1 digit(10000000);
std::cout << "Decrementing from: " << digit << " to ";
for (; digit.m_digit > 0; digit--) {}
std::cout << digit << "\n";
std::cout << "Time elapsed (WITHOUT temp variable): " << t.elapsed() << " seconds\n\n";
Timer t1;
Digit2 digit2(10000000);
std::cout << "Decrementing from: " << digit2 << " to ";
for (; digit2.m_digit > 0; digit2--) {}
std::cout << digit2 << "\n";
std::cout << "Time elapsed (WITH temp variable): " << t1.elapsed() << " seconds\n\n";
return 0;
}
[/code]
Measuring time of a debug build doesn't make much sense. If you enable optimizations (the lowest level suffices) there's no considerable difference between the two versions. Both have to create a copy of *this and decrement m_digit. They both do the same, just in a different order.
oh... now I get it. Thank you very much for your reply.
Note that this means the return value of the overloaded operator must be a non-reference, because we can’t return a reference to a local variable that will be destroyed when the function exits.
Hi,
here I am returning a local function variable by refrence, and everything is ok..! so how it's still working since it was destroyed ?
Accessing an invalid reference causes undefined behavior.
I have a quick question.
How does it understand that we want - example++ instead of ++example?
situation1 - ++(int)
situation2 - (int)++
By adding a dummy argument int to the function our both functions become different, but how does the compiler know which is which?
This is a special rule for the ++ operator. It's not something that can be done manually.
I have a query about postfix increment/decrement operator.
Kindly tell why we write it as
Digit &Digit::operator++(int)
instea of
Digit &Digit::operator++( )
mean why we use 'int'?
kindly explain.
It's been defined like that. The overload without parameters is the prefix operater, the overload with an argument is the postfix operator. There is no argument to this function. I guess this was the easiest way of differentiating them without making changes to the language.
ok
Thanks for your response
Hi Alex ,
Just wanted to clear a doubt .I have tried to overload the pre-increment operator in two version.I am not sure what is wrong with the Second version.
Kindly throw some light over this.
I have defined a Digit class having two datamembers a and b
Approach-1:
Version 2:
Approach 2 is working fine but Approah 1 is not working while overloading pre-increment ++ operator
Version 1 is recursively calling itself. That's an infinite loop.
Working Code ::Chandra Shekhar
Hi,
For the postfix overloading, you have done:
Why cant we simply do the below:
This produces the exact same output!
Hi!
Your suggestion works for the `Digit` class, so yes, we could do it. As soon as there's more than one member in the class, your suggestion no longer works and would have to be changed to what is shown in the lesson.
Hello, I've asked a question in chapter 9.3 about chaining operators earlier, so I'm still trying to wrap my head around them.
I'm guessing that the following code
would be considered as chaining, and this works because we're returning the val by reference, so each returned val after the operator-- function is the same val we've started with.(if I'm correct with my assumption here)
Now by this logic, the post-fix increment or decrement should not work for chaining(val----) and if I haven't done any mistakes while testing, it indeed does not work. (Because the value returned from the operator--(int x) is not the val we've started with, it's just a copy with a different address)
Also by doesn't work i mean it still gets evaluated correctly to the value, but it(the chained operators) does not affect the original value, as in:
However the pre-fix version works correctly, as in;
I would be glad if anyone can answer whether what I wrote is correct or not, the whole thing is too complicated! :D
Hello,
I didn't reply to your previous comment, because I don't know the definition of "chaining", or if there even is a proper definition.
I'd say chaining is whenever you apply a function immediately to the call of another function.
Your tests and explanation for the results are absolutely right.
Hey, thank you so much for replying, it feels like getting c++ correct is a really difficult task.
Thanks again :)
https://en.wikipedia.org/wiki/Method_chaining
Hey, the link you provided is great! Should have done a little more research before posting :D
Thanks for replying :)
Guys, here's a situation where a simple mistake of using prefix vs postfix caused a major memory leak in CloudFlare. Loads of private data exposed because of a simple
instead of
https://www.google.com/amp/s/blog.cloudflare.com/incident-report-on-memory-leak-caused-by-cloudflare-parser-bug/amp/
How does the syntax work out when we call the postfix version of ++? Where does the dummy variable come from to tell the compiler we want the postfix ++? How does compiler know to look to the left of operator++ to find the object, if all other unary operators have the object to the right? Thanks!
Short answer: The C++ specification specifies that it should be so, and it's up to the compiler implementers to figure this out.
is temp will be destroyed after function exits? maybe should use new Digit ?
@temp is returned by copy.
Hi,
i took @Alex's code and modified it a bit, instead of instantiating two objects of Digit, its now one:
could this enhance performance?
Line 10 creates a new instance.
yes @Alex line 47 and 53(copied into the return value) creates two instances right?
I think making copies/instances should be minimized so i took that approach, is my solution better/worse/thesame?
thank you alot nas.... looking forward for your reply.
Oops, I didn't see that.
Yes, your approach could be faster!
In reality, the compiler most likely optimizes yours and Alex' code such that they turn out the same. If it doesn't, your code is faster.
thanks you nas...
by the way, do you know a site where i can get C++ updates (cpp11-20)?
You don't install cpp standard updates, you install a compiler that supports a new standard.
Install the latest version of your compiler and you should be good to go.
thanks
You have
Remove the '&'. If your code causes errors, please post your code. An error message alone is rarely informative enough to help.
Just had a query :
For overloading Post incr/decr operator here we used Digit operator++(int) but for pre increment operator overloading we have used Digit& operator().
My question is why the return types are different ie Digit and Digit&.
Kindly help with the explanation.
Kind Regards,
A C++ Seeker :)
Postfix++ has to create a copy of the object, as it returns the old value.
++Prefix returns the value after incrementing it, so it can just return the object itself.
While Compiling Giving Warning:
|warning: reference to local variable 'temp' returned [-Wreturn-local-addr]
Also in the output screen not able to get anything