Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Increment/Decrement Operator Overloading
Implement both prefix and postfix forms of ++ and -- operators.
Overloading the increment and decrement operators
The increment (++) and decrement (--) operators come in two forms: prefix (e.g., ++x) and postfix (e.g., x++). Both forms are unary operators that modify their operands, so they're best overloaded as member functions.
Overloading prefix increment and decrement
Prefix operators are straightforward to overload. Let's create a PageNumber class for navigating a document:
#include <iostream>
class PageNumber
{
private:
int m_page{};
int m_totalPages{};
public:
PageNumber(int page, int total)
: m_page{page}, m_totalPages{total} {}
PageNumber& operator++();
PageNumber& operator--();
friend std::ostream& operator<<(std::ostream& out, const PageNumber& pn);
};
PageNumber& PageNumber::operator++()
{
if (m_page < m_totalPages)
++m_page;
return *this;
}
PageNumber& PageNumber::operator--()
{
if (m_page > 1)
--m_page;
return *this;
}
std::ostream& operator<<(std::ostream& out, const PageNumber& pn)
{
out << "Page " << pn.m_page << " of " << pn.m_totalPages;
return out;
}
int main()
{
PageNumber current{1, 5};
std::cout << current << '\n';
std::cout << ++current << '\n';
std::cout << ++current << '\n';
std::cout << --current << '\n';
return 0;
}
This outputs:
Page 1 of 5
Page 2 of 5
Page 3 of 5
Page 2 of 5
The PageNumber stays within bounds, demonstrating custom behavior for increment and decrement. We return *this so that operators can be chained together.
Overloading postfix increment and decrement
How does the compiler distinguish between prefix and postfix operators when both have the same name? C++ uses a clever solution: postfix operators take a dummy int parameter.
#include <iostream>
class PageNumber
{
private:
int m_page{};
int m_totalPages{};
public:
PageNumber(int page, int total)
: m_page{page}, m_totalPages{total} {}
PageNumber& operator++(); // prefix
PageNumber& operator--(); // prefix
PageNumber operator++(int); // postfix
PageNumber operator--(int); // postfix
friend std::ostream& operator<<(std::ostream& out, const PageNumber& pn);
};
PageNumber& PageNumber::operator++()
{
if (m_page < m_totalPages)
++m_page;
return *this;
}
PageNumber& PageNumber::operator--()
{
if (m_page > 1)
--m_page;
return *this;
}
PageNumber PageNumber::operator++(int)
{
PageNumber temp{*this};
++(*this);
return temp;
}
PageNumber PageNumber::operator--(int)
{
PageNumber temp{*this};
--(*this);
return temp;
}
std::ostream& operator<<(std::ostream& out, const PageNumber& pn)
{
out << "Page " << pn.m_page << " of " << pn.m_totalPages;
return out;
}
int main()
{
PageNumber current{3, 10};
std::cout << current << '\n';
std::cout << ++current << '\n'; // prefix: increment first, then return
std::cout << current++ << '\n'; // postfix: return first, then increment
std::cout << current << '\n';
std::cout << --current << '\n'; // prefix: decrement first, then return
std::cout << current-- << '\n'; // postfix: return first, then decrement
std::cout << current << '\n';
return 0;
}
This outputs:
Page 3 of 10
Page 4 of 10
Page 4 of 10
Page 5 of 10
Page 4 of 10
Page 4 of 10
Page 3 of 10
Let's break down what's happening in the postfix operators:
- We create a temporary copy of the current object
- We use the prefix operator to modify the current object
- We return the temporary copy (the pre-modification state)
The postfix operator returns by value (not by reference) because we can't return a reference to a local variable that will be destroyed.
Why prefix is more efficient
Notice that the postfix operators create a temporary copy of the object. This extra copy makes postfix operators less efficient than prefix operators. For built-in types like int, the compiler optimizes away this difference, but for class types, the overhead can be significant.
Prefer using prefix increment/decrement operators over postfix versions when the return value isn't needed.
A practical example
Here's a Slider class for a UI control:
#include <iostream>
class Slider
{
private:
int m_value{0};
int m_min{0};
int m_max{100};
int m_step{1};
public:
Slider(int value = 0, int min = 0, int max = 100, int step = 1)
: m_value{value}, m_min{min}, m_max{max}, m_step{step} {}
Slider& operator++()
{
m_value += m_step;
if (m_value > m_max)
m_value = m_max;
return *this;
}
Slider& operator--()
{
m_value -= m_step;
if (m_value < m_min)
m_value = m_min;
return *this;
}
Slider operator++(int)
{
Slider temp{*this};
++(*this);
return temp;
}
Slider operator--(int)
{
Slider temp{*this};
--(*this);
return temp;
}
int getValue() const { return m_value; }
friend std::ostream& operator<<(std::ostream& out, const Slider& s)
{
out << "Slider: " << s.m_value << " [" << s.m_min << "-" << s.m_max << "]";
return out;
}
};
int main()
{
Slider volume{50, 0, 100, 10};
std::cout << volume << '\n';
std::cout << ++volume << '\n'; // Increase by step
std::cout << ++volume << '\n'; // Increase by step again
Slider before{volume++}; // Save before increasing
std::cout << "Before: " << before << ", After: " << volume << '\n';
return 0;
}
This outputs:
Slider: 50 [0-100]
Slider: 60 [0-100]
Slider: 70 [0-100]
Before: Slider: 70 [0-100], After: Slider: 80 [0-100]
Summary
Prefix increment/decrement (++x, --x): Modify the object and return a reference to the modified object. Implemented as member functions returning *this by reference.
Postfix increment/decrement (x++, x--): Return the object's original value, then modify it. Take a dummy int parameter to distinguish from prefix. Create temporary copy, use prefix operator to modify, return the copy by value.
Distinguishing prefix from postfix: C++ uses a dummy int parameter for postfix operators - the parameter itself is never used, it's just a signal to the compiler.
Efficiency difference: Postfix operators are less efficient than prefix because they create a temporary copy. For built-in types, compilers optimize this away, but for class types, the overhead can be significant.
Implementation pattern: Postfix operators should call the prefix operators to avoid code duplication. Create temporary copy, call prefix operator on *this, return the temporary.
Best practice: Prefer prefix operators over postfix when the return value isn't needed, as prefix operators avoid the unnecessary copy.
Overloading increment and decrement operators allows custom classes to work naturally with loops and counter-like operations, but the postfix versions come with performance costs that should be considered.
Increment/Decrement Operator Overloading - Quiz
Test your understanding of the lesson.
Practice Exercises
Increment and Decrement Operators
Implement both prefix and postfix versions of ++ and -- operators. Understand the difference in return types and the dummy int parameter for postfix.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!