A move constructor initializes a new object with the internals of another object that is about to go out of scope. Instead of copying the members, the move constructor "transfers" them (leaving the source object in a valid but unspecified state). That transfer of members is requested by defining a constructor taking a reference to an rvalue (a type known as an rvalue reference), which is denoted by a double ampersand.
For a class where all members are ordinary value types, the distinction between copy and move appears slight, but the mechanism used is the one that later becomes useful for resource owning classes. The following program defines such a move constructor which transfers the members and then zeroes out the source.
#include <iostream>
class Point
{
private:
int x{};
int y{};
public:
Point(int xVal, int yVal)
: x{ xVal }
, y{ yVal }
{
std::cout << "ordinary constructor\n";
}
// Bind an rvalue reference to a temporary or movable object
Point(Point&& other)
: x{ other.x }
, y{ other.y }
{
other.x = 0;
other.y = 0;
std::cout << "move constructor\n";
}
void print() const
{
std::cout << x << ',' << y << '\n';
}
};
int main()
{
Point a{ 3, 5 };
Point b{ static_cast<Point&&>(a) };
a.print();
b.print();
return 0;
}
The regular constructor initializes a with 3, 5. The definition for b specifies the use of a move by casting a to an rvalue reference. The move constructor is invoked, initializes members of b from members of a, then zeroes out the members of a.
The two print statements therefore output: 0,0 for a and 3,5 for b.
The source object is still alive and available for use; it just does not hold its earlier values.
The compiler may also automatically invoke the move constructor when the source is a temporary. Because a temporary has no name that can be used after being provided to a function, the language attempts to move instead of copy.
#include <iostream>
class Point
{
private:
int x{};
int y{};
public:
Point(int xVal, int yVal)
: x{ xVal }
, y{ yVal }
{
std::cout << "ordinary constructor\n";
}
Point(Point&& other)
: x{ other.x }
, y{ other.y }
{
other.x = 0;
other.y = 0;
std::cout << "move constructor\n";
}
void print() const
{
std::cout << x << ',' << y << '\n';
}
};
Point makePoint()
{
return Point{ 7, 11 };
}
int main()
{
// Initialize p with the value returned by makePoint
Point p{ makePoint() };
p.print();
return 0;
}
Here, the function makePoint returns a temporary Point, which is used to initialize p. The rvalue used results in selection of the move constructor. The regular constructor output occurs for the temporary within makePoint, the move constructor output occurs for initialization of p, and the final print statement shows 7,11. The temporary object is zeroed out, and subsequently is destroyed.
A move constructor is allowed to leave the source object with any values possible as long as the source object is not guaranteed to be used later. Zeroing out the members is a convenient way of satisfying this condition in a non resource owning class. The moved-from object is still available for assignment and for destruction.
When both a copy and a move constructor exist, the compiler selects one based on the type of argument passed as the source value. An ordinary name refers to an lvalue, which calls copy; an rvalue refers to a value which can be modified, is called an rvalue, and calls move, unless the source is a named object which can be treated like an rvalue by casting it to an rvalue reference. If the move constructor is not found, the copy constructor is called instead, even if the source is a temporary.
Exercise: Move ConstructorsStarter code, hints and solutionOpenClose
Practice: Move Constructors
Work through the mission, reveal hints only when needed, and compare your result with the model solution.
Your challenge
Move-construct a third Point from b using std::move. Print both c and the source b, and explain why this particular class clears the source.
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>
class Point
{
private:
int x{};
int y{};
public:
Point(int xVal, int yVal)
: x{ xVal }
, y{ yVal }
{
std::cout << "ordinary constructor\n";
}
// Bind an rvalue reference to a temporary or movable object
Point(Point&& other)
: x{ other.x }
, y{ other.y }
{
other.x = 0;
other.y = 0;
std::cout << "move constructor\n";
}
void print() const
{
std::cout << x << ',' << y << '\n';
}
};
int main()
{
Point a{ 3, 5 };
Point b{ static_cast<Point&&>(a) };
a.print();
b.print();
return 0;
}Hints
static_cast<Point&&>(a) makes the move constructor eligible when b is initialized. The constructor first copies the coordinates into b, then explicitly zeroes a's members.
Predict a.print() and b.print() separately. The zeros come from this particular move constructor; not every moved-from object in C++ is required to become zero or empty.
Worked solution
A third Point is move-constructed from b using std::move. It receives 3,5 and this particular move constructor clears b to 0,0. The cleared state is a choice of this class, not a universal moved-from guarantee.
#include <utility>
#include <iostream>
class Point
{
private:
int x{};
int y{};
public:
Point(int xVal, int yVal)
: x{ xVal }
, y{ yVal }
{
std::cout << "ordinary constructor\n";
}
// Bind an rvalue reference to a temporary or movable object
Point(Point&& other)
: x{ other.x }
, y{ other.y }
{
other.x = 0;
other.y = 0;
std::cout << "move constructor\n";
}
void print() const
{
std::cout << x << ',' << y << '\n';
}
};
int main()
{
Point a{ 3, 5 };
Point b{ static_cast<Point&&>(a) };
a.print();
b.print();
Point c{std::move(b)};
std::cout << "Third: "; c.print();
std::cout << "Source b: "; b.print();
return 0;
}
Example output
ordinary constructor
move constructor
0,0
3,5
move constructor
Third: 3,5
Source b: 0,0
0 comments
No comments yet. Be the first.