Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C++  Module 12 – Move semantics and smart pointers

Writing move constructors and move assignment

Write a C++ move constructor and move assignment operator that take over a resource, keep them noexcept, and leave the moved-from object usable.

  • Intermediate
  • 20 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 8 of 20 minutes free
Free: A move constructor takes the array and leaves the source empty · Move assignment releases first, then takes over · A moved-from object can be destroyed, assigned and asked, not read · 2 more sections. With a pass: 2 in-depth parts and 3 interview answers, about 12 minutes.

What you will learn

  • Implement a move constructor and a move assignment operator that take over a resource
  • Explain when a growing std::vector moves its elements and when it copies them
  • Use a moved-from object safely by destroying it or giving it a new value
  • Detect a move that silently became a copy or that breaks on self-move

Before you start

What you’ll learn · Before you start

What you will learn

  • Implement a move constructor and a move assignment operator that take over a resource
  • Explain when a growing std::vector moves its elements and when it copies them
  • Use a moved-from object safely by destroying it or giving it a new value
  • Detect a move that silently became a copy or that breaks on self-move

Before you start

On this page

Copying a String of 28 characters allocates a new array of 29 chars and copies every one; moving it copies two members and allocates nothing, whatever the length. A move constructor: A constructor T(T&& other) that builds a new object by taking over the resources of other, an rvalue the caller has finished with, instead of copying them. It leaves other valid, usually empty, because other's destructor still runs., T(T&& other) noexcept, builds a new object by taking over the resources of other, an object the caller has finished with. A move assignment operator: The member T& operator=(T&& other) that gives an existing object the resources of other: it releases what the object already holds, takes over what other holds and leaves other valid. It must also leave a = std::move(a) harmless. operator, T& operator=(T&& other) noexcept, does the same for an object that already exists: it releases what it holds, then takes over what other holds. Both must leave other valid, usually empty, because its destructor still runs, and both should be noexcept: A specifier that promises a function lets no exception out, as in Buffer(Buffer&&) noexcept. If one tries to leave such a function, std::terminate ends the program. A destructor is noexcept unless it says otherwise. More in “Destructors and RAII in C++”, because a growing std::vector copies, where it can, every element whose move constructor might throw.

Writing move constructors by hand belongs in the few classes that own a resource directly, such as a raw array or a file handle; every other class gets correct moves from its members.

A move constructor takes the array and leaves the source empty

String owns a heap array of characters, so the compiler’s member-by-member copy would share one array between two objects; it needs all five special member function: One of the member functions the compiler can write for a class by itself: the default constructor, the destructor, the copy constructor, copy assignment, the move constructor and move assignment. The compiler's versions work member by member. More in “The rule of zero, three and five in C++”. Each of them prints its name:

string_trace.cpp C++ · string_trace.cpp
// String owns a heap array of chars. Its five special member functions print their names, so the run shows which
// one each line of main calls.
#include <cstddef>
#include <cstring>
#include <print>
#include <utility>

class String {
public:
    String(const char* text) : size_{std::strlen(text)}, data_{new char[size_ + 1]} {
        std::memcpy(data_, text, size_ + 1);
    }
    ~String() { delete[] data_; }  // delete[] on a null pointer does nothing

    String(const String& other) : size_{other.size_}, data_{new char[size_ + 1]} {
        std::memcpy(data_, other.data_, size_ + 1);
        std::println("    copy constructor: a new array of {} chars", size_ + 1);
    }

    String(String&& other) noexcept
        : size_{std::exchange(other.size_, 0)}, data_{std::exchange(other.data_, nullptr)} {
        std::println("    move constructor: took the array, left the source empty");
    }

    String& operator=(const String& other) {
        String copy{other};  // the only step that can throw, before *this changes
        swap(copy);          // copy now holds the old array and frees it
        std::println("    copy assignment");
        return *this;
    }

    String& operator=(String&& other) noexcept {
        if (this != &other) {
            delete[] data_;
            data_ = std::exchange(other.data_, nullptr);
            size_ = std::exchange(other.size_, 0);
        }
        std::println("    move assignment: freed the old array, took the source's");
        return *this;
    }

    void swap(String& other) noexcept {
        std::swap(size_, other.size_);
        std::swap(data_, other.data_);
    }
    const char* c_str() const { return data_ ? data_ : ""; }
    std::size_t size() const { return size_; }

private:
    std::size_t size_;
    char* data_;
};

int main() {
    String a = "a string long enough to show";
    std::println("String b = a;");
    String b = a;
    std::println("String c = std::move(a);");
    String c = std::move(a);
    std::println("b = std::move(c);");
    b = std::move(c);
    std::println("a = String{{\"reused\"}};");
    a = String{"reused"};
    std::println("a = \"{}\", b = \"{}\", c.size() = {}", a.c_str(), b.c_str(), c.size());
}
Which function each line calls Python · string_trace_session.py
from session import run

# Built with AddressSanitizer as well, which would stop the run at a double delete[] or a read of freed memory.
run("""
clang++ -std=c++23 -Wall -Wextra -fsanitize=address string_trace.cpp -o string_trace && ./string_trace
g++-16 -std=c++23 -Wall -Wextra -fsanitize=address string_trace.cpp -o string_trace-gcc
./string_trace > clang.txt && ./string_trace-gcc > gcc.txt && diff clang.txt gcc.txt && echo "GCC prints the same"
""")

Output

$ clang++ -std=c++23 -Wall -Wextra -fsanitize=address string_trace.cpp -o string_trace && ./string_trace
String b = a;
    copy constructor: a new array of 29 chars
String c = std::move(a);
    move constructor: took the array, left the source empty
b = std::move(c);
    move assignment: freed the old array, took the source's
a = String{"reused"};
    move assignment: freed the old array, took the source's
a = "reused", b = "a string long enough to show", c.size() = 0
$ g++-16 -std=c++23 -Wall -Wextra -fsanitize=address string_trace.cpp -o string_trace-gcc
$ ./string_trace > clang.txt && ./string_trace-gcc > gcc.txt && diff clang.txt gcc.txt && echo "GCC prints the same"
GCC prints the same

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 string_trace_session.py

Step through main and watch the arrays change hands:

Interactive · step by step Which function each line of main calls Step through main of string_trace.cpp. The left column holds the String objects, the right one their heap arrays.
Which function each line of main calls, step 7 of 7. Showing String a (size 6), array 1, String b (size 28), String c (size 0, nullptr), array 3. In focus: String c.ownsownsString asize 6array 129 charsString bsize 28String csize 0, nullptrarray 37 chars: "reused"

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. String a = …. a is built from a text of 28 characters, so its constructor allocates an array of 29 chars on the heap: the characters and the terminating zero.
  2. String b = a;. a is an lvalue, so the copy constructor runs. It allocates a second array of 29 chars and copies every character into it; the run printed copy constructor: a new array of 29 chars.
  3. String c = std::move(a);. std::move(a) is an xvalue, so the move constructor runs. std::exchange hands a's pointer and size to c and leaves a with nullptr and 0: two members copied, nothing allocated.
  4. b = std::move(c);. b already owns array 2, so the move assignment frees it first, then takes c's pointer and size. Without that delete[], array 2 would leak.
  5. String{"reused"}. The right-hand side is a temporary String with an array of its own, 7 chars for the 6 letters of reused and the zero.
  6. a = String{"reused"};. a was moved from, and it accepts a new value by move assignment like any String: delete[] on its null pointer does nothing, and it takes the array of the temporary, which then ends with the statement.
  7. The end of main. The run printed a = "reused", b = "a string long enough to show", c.size() = 0. Each String then deletes what it owns, and c deletes a null pointer, which does nothing. AddressSanitizer found no error.

The diagram freezes step 3, the move constructor:

Before the move, a points to a heap array; after it, c points to the same array and a holds size 0 and a null pointer.String c = std::move(a);Beforeasize_ = 28data_heap array29 charsAfter the move constructorcsize_ = 28data_the same arraynot copiedasize_ = 0data_ = nullptrempty but valid:its destructor runsdelete[] on nullptr,which does nothingThe move copies two membersand allocates nothing.

What the move constructor does to the two objects

Text description of the diagram

The diagram is titled "String c = std::move(a);" and has two parts.

  1. Before: the object a has size_ = 28 and a data_ pointer that points to a heap array of 29 characters.
  2. After the move constructor: the object c has size_ = 28 and a data_ pointer to the same heap array, whose 29 characters were not copied. The object a now has size_ = 0 and data_ = nullptr; it is empty but valid, because its destructor runs delete[] on nullptr, which does nothing.

A line at the bottom says that the move copies two members and allocates nothing.

The move constructor copies two members and allocates nothing. std::exchange(x, v), from <utility>, stores v in x and returns the old value, so each member of other is taken and reset in one expression of the member initialiser list: c gets the pointer and the size, and a is left with a null pointer and a size of 0. The cost is the same for a string of 28 characters or of 28 million. Nothing in it can throw, which is why it can promise noexcept. The source stays a valid String: its destructor deletes a null pointer, which does nothing, and c.size() reports 0 at the end of the run.

Checkpoint

Check: A move constructor that takes over

  1. Question 1 of 2 How many heap allocations does String's move constructor make for a string of 28 characters?

    Type a number.

    A hint

    Look for new in its member initialiser list.

    Show the answer: Question 1 of 2

    Answer: 0 allocations

    It takes the pointer and the size with std::exchange and resets the source, so nothing is allocated, and the cost is the same for 28 characters or 28 million.

  2. Question 2 of 2 Which member initialiser list gives String a correct move constructor?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: : size_{std::exchange(other.size_, 0)}, data_{std::exchange(other.data_, nullptr)}

    A move takes the resource and resets the source in the same step. std::exchange(x, v) stores v in x and returns the old value, which is exactly that.

Move assignment releases first, then takes over

An existing object already owns an array, so the move assignment operator frees it before taking the one from other; otherwise every assignment would leak. The line a = String{"reused"}; shows the other half of the contract: a had been moved from, and it accepts a new value like any other String, because a moved-from object is still a complete object.

The if (this != &other) test handles self-move: Moving an object into itself, as in a = std::move(a), usually through two names for one object. A move assignment that frees its own resource before taking other's would take back what it has just freed, so it tests for it or is written to need no test., an object moved into itself. Without it, the first statement would free the array that the next statement takes back. Step through a = std::move(a) with the test, then without it:

Interactive · step by step A String moved into itself Step through a = std::move(a), first with the self-move test and then without it. The right column holds the array.
A String moved into itself, step 5 of 5. Showing String a (size 28, freed pointer), a's array (freed), other. In focus: String a, a's array.ownsrefers toString asize 28, freed pointera's arrayfreedotherthe same object as a

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. a = std::move(a). The operator is called on a, and its parameter other refers to a as well: this and &other are the same address.
  2. With the test. if (this != &other) is false, so the body is skipped. a keeps its array and its 28 characters, and the operator returns *this.
  3. Without it: delete[]. Without the test, the first statement runs delete[] data_ and frees a's array. other.data_ is the same member, so it now holds the freed address too.
  4. The pointer comes back. data_ = std::exchange(other.data_, nullptr); stores nullptr in other.data_, which is data_, and then assigns the old value back: a holds the freed address again.
  5. The second delete[]. When a is destroyed, its destructor runs delete[] on the same address a second time: a double free, which is undefined behaviour. The test at the top of the operator is what prevents it.

There are three common ways to write a self-move-safe move assignment:

  • A self-check, as here, skips all the work when other is the object itself.
  • Move-and-swap moves other into a temporary and swaps with it, so self-move needs no test at all.
  • Swap-only exchanges the two objects’ contents and lets other free the old array whenever it is destroyed.

The copy assignment operator uses copy-and-swap: A way to write copy assignment: make a complete copy of the source, swap its members with this object's, and let the copy's destructor free the old resource. It needs no test for self-assignment and changes nothing if the copy throws. More in “The rule of zero, three and five in C++”, as in the rule-of-three lesson, so a failed allocation leaves *this untouched.

Key idea

Move assignment frees what the object holds, then takes what other holds, and a = std::move(a) must leave the object as it was.

Checkpoint

Check: Move assignment and self-move

  1. Question 1 of 2 b already owns array 2 when b = std::move(c); runs. What happens to array 2?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The move assignment deletes it before taking c's array

    Release first, then take over: an existing object owns something, and the move assignment must not lose it.

  2. Question 2 of 2 Without the if (this != &other) test, what happens on a = std::move(a)?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The array is freed, the freed address is stored back in a, and the destructor frees it a second time

    When other is the object itself, the delete[] frees the array that the next statement takes back. The test at the top of the operator leaves the object as it was.

A moved-from object can be destroyed, assigned and asked, not read

Rule C.64 of the C++ Core Guidelines asks a move to leave its source in a valid state: its invariants hold, so it can be destroyed, assigned a new value, and passed to any function that has no precondition about its value, such as size() or clear(). Reading its old value is the bug, a use after move: Reading a variable after std::move has handed its contents to another object. The moved-from object is still valid, but its value is not what it was: in this track's example, an empty string and an empty vector. More in “clang-tidy, clang-format and static analysis”: the code compiles, and what it reads depends on the type and the library. Step through what the run did with its moved-from strings:

Interactive · stage by stage What a moved-from String may still do Step through the uses of c after b = std::move(c);, from always safe to the one that is a bug.
What a moved-from String may still do, step 6 of 6. Showing c, moved from, Destroy it, Give it a new value, Call size() or clear(), Read its old value. In focus: c, moved from.c, moved fromsize 0, a null pointerDestroy itits destructor is safeGive it a new valueas a = String{"reused"} didCall size() or clear()no precondition on the valueRead its old valuea use after move: a bug

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Moved from. After b = std::move(c);, c holds a null pointer and a size of 0. It is still a complete String, in scope, and its destructor will run at the end of main.
  2. Destroy it. Destroying a moved-from object is always allowed: String's destructor runs delete[] on a null pointer, which does nothing.
  3. Give it a new value. Assigning is allowed too: a, moved from by String c = std::move(a);, then took the value of String{"reused"} and printed reused.
  4. Ask it. A function with no precondition about the value works: c.size() printed 0. For library types, size(), empty() and clear() are of this kind.
  5. Not its old value. Reading the old value is the bug. A library object is only valid with an unspecified value, so what such a read finds depends on the library. String leaves an empty object, a state its documentation can promise.
  6. Types that say more. A few standard types promise a state: a moved-from std::unique_ptr is null, and a moved-from std::optional still holds a value, the one that was moved from.

For standard library types, the standard promises a valid but unspecified state: What the standard promises for a moved-from library object unless it says more: its invariants hold, so it can be destroyed, assigned or asked for its size, but its value is not specified and must not be relied on. unless it says more. A few types do say more: a moved-from std::unique_ptr is null, and a moved-from std::optional still holds a value, the moved-from one. For your own types, state the moved-from state in the class’s documentation; “empty”, as String does it, is the easiest promise to keep and to test.

Checkpoint

Check: What a moved-from object may do

  1. Question 1 of 2 c has just been moved from by b = std::move(c);. Which of these are safe with String?

    Choose every answer that is right.

    Show the answer: Question 1 of 2

    Answer:

    • c.size()
    • c = String{"new"};
    • Letting c go out of scope

    A moved-from String is empty but valid: destroy it, assign it or ask it questions with no precondition, and never expect its old value.

  2. Question 2 of 2 std::optional<std::string> maybe2 = std::move(maybe); What does maybe.has_value() return afterwards?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: true, because a moved-from optional still holds a value, the moved-from string

    std::unique_ptr and std::optional are two types that promise their moved-from state: null for the first, still a value for the second.

Why a growing vector needs noexcept moves

When a std::vector runs out of room, it allocates a bigger array and transfers every element into it. If a move threw halfway, the old array would already be partly emptied, and push_back could no longer promise to leave the vector as it was. So the vector transfers each element with std::move_if_noexcept, which moves when the move constructor cannot throw, or when the type cannot be copied at all, and copies otherwise. Step through one growth:

Interactive · stage by stage A full vector of String grows Step through one push_back on a full vector. The third stage shows how each element reaches the new array.
A full vector of String grows, step 6 of 6. Showing A full vector of String, Allocate a bigger array, Transfer each element (String: moved), Free the old array. In focus: Free the old array.A full vector of Stringno room for one moreAllocate a bigger arraythe old one stays wholeTransfer each elementString: movedFree the old arrayafter every transfer

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. No room left. push_back is called on a vector of String whose array is full, so every element has to go to a new, bigger array.
  2. A bigger array. The vector allocates the new array first. Until the transfer has finished, the old array still holds every element.
  3. Moves that cannot throw. String's move constructor is noexcept, so std::move_if_noexcept gives an rvalue and each element is moved: two members, no allocation.
  4. Moves that might throw. If the move constructor might throw and the type can be copied, std::move_if_noexcept gives a const reference, and each element is copied instead. A failed copy then leaves the old array whole, so push_back can leave the vector as it was.
  5. Types that cannot be copied. A type that cannot be copied is moved even when its move might throw, because there is nothing else to do. A failure halfway would leave the old array partly emptied.
  6. Free the old array. Once every element has arrived, the vector destroys the old ones and frees their array. That is why rule C.66 of the Core Guidelines says: make move operations noexcept.

Rule C.66 of the Core Guidelines follows from it: make move operations noexcept. A move constructor that the compiler writes is noexcept exactly when the moves of all the members are. One that you write is noexcept only if you say so. In a template, the honest promise is a conditional noexcept: A noexcept specifier with a condition, such as noexcept(std::is_nothrow_move_constructible_v<T>), so that a template promises not to throw exactly when the type it wraps makes the same promise., such as noexcept(std::is_nothrow_move_constructible_v<T>), which promises exactly what T promises.

Checkpoint

Check: Why moves are noexcept

  1. Question 1 of 2 A type cannot be copied, and its move constructor may throw. What does a growing std::vector do with its elements?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It moves them anyway, and a failure halfway can leave the old array partly emptied

    The vector copies only when it can; for a move-only type it moves, whatever the move promises.

  2. Question 2 of 2 A class template Box<T> writes its own move constructor. Which exception specification is honest for every T?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: noexcept(std::is_nothrow_move_constructible_v<T>)

    A wrapper can only promise what the wrapped type promises; a conditional noexcept says exactly that.

Practise: four rules of a move

Questions on the four ideas above, weakest first; one you miss comes back before the end.

Practice that adapts to you

Practise this lesson

13 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 13 What does std::exchange(other.data_, nullptr) do?

    Choose one answer.

    Show the answer: Question 1 of 13

    Answer: Stores nullptr in other.data_ and returns the value it held before

    std::exchange(x, v) stores v in x and returns the old value, so a member is taken and reset in one expression.

  2. Question 2 of 13 What goes wrong with this move constructor?

    Read the code, then choose one answer.

    String(String&& other) noexcept
        : size_{other.size_}, data_{other.data_} {}
    Show the answer: Question 2 of 13

    Answer: Both objects now point to one array, so both destructors delete it

    It takes the pointer but does not reset the source. When both objects are destroyed, the same array is deleted twice. std::exchange(other.data_, nullptr) takes the pointer and resets it in one expression.

  3. Question 3 of 13 Why can String's move constructor promise noexcept while its copy constructor cannot?

    Choose one answer.

    Show the answer: Question 3 of 13

    Answer: The move only copies two members, while the copy allocates with new, which can throw std::bad_alloc

    A noexcept promise must be true. The move takes and resets members; the copy asks for memory and can fail.

  4. Question 4 of 13 Why does String's move assignment start with if (this != &other)?

    Choose one answer.

    Show the answer: Question 4 of 13

    Answer: So that a = std::move(a) does not free the array it is about to take back

    When other is the object itself, the delete[] in the operator frees the very array that the next statement would take over. The check makes self-move leave the object unchanged.

  5. Question 5 of 13 After String c = std::move(a); the program runs a = String{"reused"};. What does the move assignment do?

    Choose one answer.

    Show the answer: Question 5 of 13

    Answer: It deletes a's null pointer, which does nothing, and takes the temporary's array

    Release then take over works on a moved-from object too: releasing a null pointer is a no-op.

  6. Question 6 of 13 A teammate writes String's move assignment as swap-only. Where does the array that *this held go?

    Read the code, then choose one answer.

    String& operator=(String&& other) noexcept {
        swap(other);
        return *this;
    }
    Show the answer: Question 6 of 13

    Answer: Into other, which frees it when it is destroyed or assigned again

    swap exchanges the members, so the old array now belongs to other and is freed whenever other is destroyed or assigned again.

  7. Question 7 of 13 What must a move constructor leave in the object it moved from?

    Choose one answer.

    Show the answer: Question 7 of 13

    Answer: A valid object, usually empty, that can still be destroyed and assigned

    The source stays alive until the end of its scope, and its destructor runs then. Leaving it empty, with a null pointer and a size of 0 as String does, keeps that destructor and any later assignment correct.

  8. Question 8 of 13 Which of these are safe on a std::string that has just been moved from?

    Choose every answer that is right.

    Show the answer: Question 8 of 13

    Answer:

    • Letting it go out of scope
    • Assigning it a new value
    • Calling size()

    A moved-from library object is valid but unspecified: functions without preconditions, assignment and destruction all work. Its old characters are gone, and s[0] even needs a non-empty string to be valid.

  9. Question 9 of 13 You write a class that owns a buffer. What should its moved-from state be?

    Choose one answer.

    Show the answer: Question 9 of 13

    Answer: Empty, written in the class's documentation, with every invariant still true

    A move should leave its source valid (rule C.64). For your own types, say what is left; "empty" is the usual and safest answer.

  10. Question 10 of 13 When is a move constructor that the compiler writes noexcept?

    Choose one answer.

    Show the answer: Question 10 of 13

    Answer: Exactly when the move constructors of all its members are noexcept

    A class that follows the rule of zero gets noexcept moves only if every member has them.

  11. Question 11 of 13 For a type that can be copied and whose move constructor may throw, what does std::move_if_noexcept(x) give a growing vector?

    Choose one answer.

    Show the answer: Question 11 of 13

    Answer: A const reference, so the element is copied

    move_if_noexcept returns const T& when the move may throw and a copy is possible, and T&& otherwise. Copying keeps the old array intact if a transfer fails, which is what lets push_back leave the vector as it was.

  12. Question 12 of 13 A class holds a std::string and a std::vector<int> and declares no special member function. Is its move constructor noexcept?

    Choose one answer.

    Show the answer: Question 12 of 13

    Answer: Yes, because the standard makes the move constructors of both members noexcept

    An implicitly declared move constructor is noexcept when every member's move constructor is, and the standard declares both std::string's and std::vector's move constructors noexcept. A growing std::vector of this class therefore moves its elements.

  13. Question 13 of 13 Box<T> declares its move constructor noexcept(std::is_nothrow_move_constructible_v<T>). For which T does a growing vector of Box<T> copy its elements?

    Choose one answer.

    Show the answer: Question 13 of 13

    Answer: For a T that can be copied and whose move may throw

    A conditional noexcept passes T's promise through, so Box<T> is transferred exactly as T would be.

noexcept and std::vector growth, measured on two libraries Pro: opens with a Pro, Premium or Ultimate pass

Whether std::vector moves or copies its elements is decided at compile time from the element type alone, and the same class can get a different answer from each standard library. This part measures the difference in heap allocations and compares four ways to fix it.

In this part:

  • How std::move_if_noexcept chooses, case by case
  • A rule-of-zero class that copies on one library
  • Four fixes and what each costs, measured
  • Catching the silent copy at build time
  • noexcept for templates that write their moves by hand

6 runnable examples (C++, Python) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Self-move and moved-from objects: two failures reproduced Pro: opens with a Pro, Premium or Ultimate pass

A move leaves its source behind, still alive, still in scope and still callable, and that is where move bugs hide. Both failures below are reproduced with Apple clang 21 and GCC 16, then fixed.

In this part:

  • A self-move in an unordered erase, caught by AddressSanitizer
  • Three ways to make move assignment safe for self-move
  • Valid but unspecified, measured on two libraries
  • Your turn: a Buffer that a vector grows without copying

6 runnable examples (C header, C++, Python) · about 5 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Interview questions

  1. Fresher · Warm-up

    What can you still do with an object after it has been moved from?

    Anything that does not depend on its old value. A moved-from object is still a complete, valid object: its destructor will run, you can assign it a new value, and you can call member functions that have no preconditions, such as size(), empty() or clear() on a container. What you cannot rely on is its contents. For most standard library types the standard says only "valid but unspecified", so a moved-from string is usually empty but is not promised to be. A few types promise more: a moved-from std::unique_ptr is null, and a moved-from std::optional still holds a value. For your own classes, document the moved-from state, preferably empty, and keep the class invariant true in it.

  2. Mid to senior

    Filling a std::vector<Job> is clearly slower in the Linux build than on macOS. Job follows the rule of zero and holds a std::string and a std::deque<int>. Explain the difference, how you would confirm it, and which fix you would choose.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Fresher to mid

    remove_at(v, i) runs v[i] = std::move(v.back()); v.pop_back(); on a vector of a hand-written String class. Removing the last element crashes with a double free under AddressSanitizer, and removing any other element works. Explain the crash and give two fixes, with their trade-offs.

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    Design the move operations of a FileHandle class that owns a POSIX file descriptor, an int. What state does a moved-from handle have, what may callers still do with it, and how do your tests prove that no descriptor is closed twice or leaked?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A move constructor takes over the source’s resources and resets the source, here with std::exchange; it copies two members and allocates nothing.
  • A move assignment operator releases what it holds, then takes over, and must survive a = std::move(a).
  • A moved-from object can be destroyed, assigned and asked for its size; its old value is gone.
  • std::vector moves its elements while it grows only if their move constructor is noexcept or they cannot be copied.
  • Write noexcept on hand-written moves, and noexcept(std::is_nothrow_move_constructible_v<T>) in templates.
  • Writing move constructors and move assignment by hand is for classes that own a resource directly; the others get correct moves from their members.

Terms in this lesson

conditional noexcept
A noexcept specifier with a condition, such as noexcept(std::is_nothrow_move_constructible_v<T>), so that a template promises not to throw exactly when the type it wraps makes the same promise.
move assignment operator
The member T& operator=(T&& other) that gives an existing object the resources of other: it releases what the object already holds, takes over what other holds and leaves other valid. It must also leave a = std::move(a) harmless.
move constructor
A constructor T(T&& other) that builds a new object by taking over the resources of other, an rvalue the caller has finished with, instead of copying them. It leaves other valid, usually empty, because other's destructor still runs.
self-move
Moving an object into itself, as in a = std::move(a), usually through two names for one object. A move assignment that frees its own resource before taking other's would take back what it has just freed, so it tests for it or is written to need no test.
valid but unspecified state
What the standard promises for a moved-from library object unless it says more: its invariants hold, so it can be destroyed, assigned or asked for its size, but its value is not specified and must not be relied on.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.