Step 1 of 7 · Reading · ~4 min
Learn
Modern C++ Patterns
Move Semantics and Rvalue References
You will be able to write the move constructor and move assignment that finish the Rule of Five, say what std::move really does, and read a copy-and-move counter.
You have written std::move(p) since smart pointers and wrote the copy half of the Rule of Five last lesson. This lesson explains std::move and writes the other two members.
Two kinds of expression, three kinds of reference
An lvalue has a name and an address (s). An rvalue is a temporary (std::string("x"), a function's return value). T& binds lvalues, const T& both, and the new T&& only rvalues. Choosing among same-named functions by their arguments is overload resolution, so void f(const std::string&) can run for f(s) and void f(std::string&&) for f(std::string("x")).
std::move is only a cast
std::move(x) moves nothing. It casts x to an rvalue so overload resolution picks the T&& member, which does the stealing. The clearest case is a type that cannot be copied. Handle owns a numeric resource id, with -1 meaning "owns nothing":
class Handle {
public:
explicit Handle(int id) : id_(id) {}
~Handle() { close_(); }
Handle(const Handle&) = delete;
Handle& operator=(const Handle&) = delete;
Handle(Handle&& o) noexcept : id_(o.id_) { o.id_ = -1; }
Handle& operator=(Handle&& o) noexcept {
if (this != &o) { close_(); id_ = o.id_; o.id_ = -1; }
return *this;
}
private:
void close_() { if (id_ >= 0) std::cout << "close " << id_ << "\n"; id_ = -1; }
int id_;
};
The move constructor takes the resource and resets the source, so its destructor finds nothing to release. The move assignment first releases what the target owns. Trace it:
Handle a(7);
Handle b = std::move(a); // b owns 7, a owns nothing
Handle c(9);
c = std::move(b); // prints "close 9", then c owns 7
// scope ends: c prints "close 7"; a and b print nothing
Handle d = a; is a compile error; the cast is the only way to hand a over.
The moved-from a is valid but empty. For your own class you define that state (-1 here); for library types it is unspecified, so assign to it or destroy it, never read it. std::move on a const object gives a const T&&, which cannot bind to T&&, so const std::string k = "abc"; std::string d = std::move(k); quietly copies.
Where the compiler moves for you
A returned value and a temporary argument are already rvalues, and in return h; the compiler treats the local h as one too. That is why a function can return a Handle by name. Copy elision means the compiler builds the object where the caller wants it and skips the copy or move. return h; allows that; return std::move(h); hides it and forces a real move.
noexcept decides how a vector grows
noexcept promises a function will not throw. When a std::vector outgrows its block it must relocate (new bigger block, transfer each element, free the old one). A failed copy leaves the original intact, but a move that throws has already damaged its source, so the vector moves only if the move constructor is noexcept, and otherwise copies. Count with a bare struct:
static int n_copy = 0, n_move = 0;
struct Tok {
Tok() {} Tok(const Tok&) { ++n_copy; }
Tok(Tok&&) noexcept { ++n_move; }
};
std::vector<Tok> v;
for (int i = 0; i < 4; ++i) v.push_back(Tok());
With noexcept this costs 0 copies and 7 moves: four pushes plus three relocations (capacity 1 to 2 moves one element, 2 to 4 moves two). Delete noexcept and it costs 3 copies and 4 moves; nothing warns you.
The sink parameter idiom takes by value, then moves in: explicit Account(Tok p) : tag(std::move(p)) {}. A named Tok t costs 1 copy and 1 move; std::move(t) costs 0 and 2. std::swap(a, b) is three moves, or three copies without move members.
Common mistakes
- Not resetting the source. Two objects release one resource:
close 7prints twice, or a heap block is freed twice and the program aborts. - Leaving out
noexcept. Every value is right, but vector growth copies, so the copy counter is above 0. - Forgetting
std::moveat a call site.to = from;copies even when move members exist. - Skipping the release in move assignment. What the target owned before is leaked.
Your exercise
Give a copy-only IntBuffer its move members and fix the two call sites that still copy; the counters show which ran.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…