Copy and Move Semantics

Move Constructors

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
Guided practiceBeginner · 10–15 min

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

  1. Run the starter as a baseline, then predict the change requested in Your challenge.
  2. Make that change in your editor; use the lesson-specific hints if you need help.
  3. 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

Hint 1

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.

Hint 2

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.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.