Assignment operators differ from the arithmetic operators previously shown. The expression a + b returns a new value and leaves a and b unchanged; the expression a = b (or a += b) actually modifies the object on the left hand side. Due to this side effect, the assignment operators almost always should be written as member functions that return a reference to the object being modified.
The simplest among them is the copy assignment operator. The conventional signature for a class Point might be:
// Bind by reference without allowing modification through it
Point& operator=(const Point& other)
{
x = other.x;
y = other.y;
return *this;
}
The argument is taken by reference to const, so that the object passed is not copied. In the body of the function, each member of this object is then overwritten with the equivalent member of other, and a reference to the current object is returned. Returning a reference is responsible for chained assignment:
a = b = c;
The right most assignment is evaluated first and returns a reference to b, which then becomes the right hand side object for the next assignment.
An example fully declaring the copy assignment operator would look like:
#include <iostream>
struct Point
{
int x{};
int y{};
// Define how this operator works for the class
Point& operator=(const Point& other)
{
x = other.x;
y = other.y;
return *this;
}
};
int main()
{
Point a{ 1, 2 };
Point b{ 3, 4 };
Point c;
c = b;
a = b = c;
std::cout << a.x << ' ' << a.y << '\n';
return 0;
}
Inside the operator=, members belonging to the current object this are written over with corresponding members belonging to the object other. Since a reference is returned, the second assignment receives a valid Point object as the left hand side operand.
When a class allocates a resource, we need to make sure our copy assignment operator doesn't perform self assignment.
Point& operator=(const Point& other)
{
if (this == &other)
return *this;
// release any resource the current object owns
// acquire a copy of the resource owned by other
// ...
return *this;
}
Otherwise, the operation p = p would destroy the memory pointed to by p's data members, and then try to write to it.
Compound assignment operators (+=, -=, *=, etc.) should also be implemented as members that return a reference. A typical implementation for Point would be:
// Add the value and store the result back in the same variable
Point& operator+=(const Point& other)
{
x += other.x;
y += other.y;
return *this;
}
Once we've defined +=, the ordinary + operator can be defined in terms of it; that helps guarantee that both are consistent.
Point operator+(Point lhs, const Point& rhs)
{
lhs += rhs;
return lhs;
}
In the function operator+, lhs is taken by value so that the function gets its own copy of the left hand object. It adds rhs to lhs, and returns the modified lhs. This is really equivalent to our free function operator+ except that now the addition is expressed as a compound assignment operator.
With all these members, we now have a complete and consistent set of operators.
#include <iostream>
// Define the members that belong to this type
struct Point
{
int x{};
int y{};
Point& operator=(const Point& other)
{
x = other.x;
y = other.y;
return *this;
}
Point& operator+=(const Point& other)
{
x += other.x;
y += other.y;
return *this;
}
};
Point operator+(Point lhs, const Point& rhs)
{
lhs += rhs;
return lhs;
}
int main()
{
Point a{ 1, 2 };
Point b{ 3, 4 };
a += b;
Point c{ a + b };
std::cout << a.x << ' ' << a.y << '\n';
std::cout << c.x << ' ' << c.y << '\n';
return 0;
}
The assignment operators take a reference to themselves and return it, so that you can write p1 = p2 = Point{ 1, 2 }.
We define each simple arithmetic operator in terms of the compound assignment operator in order to avoid duplicate calculations.
Exercise: Assignment OperatorsStarter code, hints and solutionOpenClose
Practice: Assignment Operators
Work through the mission, reveal hints only when needed, and compare your result with the model solution.
Your challenge
Rewrite both assignment statements as explicit member operator= calls, preserving their grouping and final result.
Mission checklist
- Run the starter as a baseline, then predict the change requested in Your challenge.
- Make that change in your editor; use the lesson-specific hints if you need help.
- Compile and run your answer, then compare with the worked solution and explain the result.
Starter program
This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.
#include <iostream>
struct Point
{
int x{};
int y{};
// Define how this operator works for the class
Point& operator=(const Point& other)
{
x = other.x;
y = other.y;
return *this;
}
};
int main()
{
Point a{ 1, 2 };
Point b{ 3, 4 };
Point c;
c = b;
a = b = c;
std::cout << a.x << ' ' << a.y << '\n';
return 0;
}Hints
Translate c = b to c.operator=(b). The member function copies the fields and returns a reference to the target object through *this.
Group a = b = c as a = (b = c). The reference returned by the inner assignment becomes the source for the outer assignment.
Worked solution
The assignment expressions are written as explicit member calls. c.operator=(b) assigns to c, and a.operator=(b.operator=(c)) groups the chained assignment from right to left. Returning *this permits the inner result to act as the outer source.
#include <iostream>
struct Point
{
int x{};
int y{};
// Define how this operator works for the class
Point& operator=(const Point& other)
{
x = other.x;
y = other.y;
return *this;
}
};
int main()
{
Point a{ 1, 2 };
Point b{ 3, 4 };
Point c;
c.operator=(b);
a.operator=(b.operator=(c));
std::cout << a.x << ' ' << a.y << '\n';
return 0;
}
Example output
3 4
0 comments
No comments yet. Be the first.