Moving transfers a resource from an object that's about to die (or that you no longer need) instead of copying it. A std::vector move is three pointer copies — no matter how many elements it holds.
Copy vs move
#include <iostream>
#include <string>
#include <utility>
#include <vector>
int main() {
std::vector<int> a(1'000'000, 7);
std::vector<int> b = a; // copy: allocates and copies a million ints
std::vector<int> c = std::move(a); // move: steals a's buffer
std::cout << a.size() << ' ' << b.size() << ' ' << c.size() << '\n'; // a is now empty (valid, unspecified)
std::string s = "a fairly long string that will not fit in SSO";
std::vector<std::string> names;
names.push_back(s); // copies
names.push_back(std::move(s)); // moves — s is left valid but unspecified
std::cout << names.size() << ' ' << names[1].size() << '\n';
}0 1000000 1000000
2 45Writing move operations
#include <iostream>
#include <utility>
class Buffer {
int* data_ = nullptr;
std::size_t size_ = 0;
public:
explicit Buffer(std::size_t n) : data_(new int[n]{}), size_(n) {}
~Buffer() { delete[] data_; }
Buffer(const Buffer& o) : data_(new int[o.size_]), size_(o.size_) {
std::copy(o.data_, o.data_ + size_, data_);
std::cout << "copy\n";
}
Buffer(Buffer&& o) noexcept // move constructor
: data_(std::exchange(o.data_, nullptr)), size_(std::exchange(o.size_, 0)) {
std::cout << "move\n";
}
Buffer& operator=(Buffer&& o) noexcept { // move assignment
if (this != &o) {
delete[] data_;
data_ = std::exchange(o.data_, nullptr);
size_ = std::exchange(o.size_, 0);
}
return *this;
}
Buffer& operator=(const Buffer&) = delete; // kept short for the example
std::size_t size() const { return size_; }
};
Buffer make() { return Buffer(8); } // guaranteed elision: no copy, no move
int main() {
Buffer a(4);
Buffer b = std::move(a); // move
Buffer c = b; // copy
Buffer d = make(); // nothing printed
d = std::move(c); // move assignment
std::cout << a.size() << ' ' << b.size() << ' ' << c.size() << ' ' << d.size() << '\n';
}move
copy
0 4 0 4Steal
Take the source's pointers/handles.
Reset the source
Leave it in a valid, destructible state (usually empty) —
std::exchange(o.p, nullptr)does both in one step.Mark
noexceptstd::vectoronly moves elements during reallocation if the move constructor isnoexcept; otherwise it copies to keep the strong guarantee.
Why noexcept matters
#include <iostream>
#include <vector>
struct Safe { Safe() = default; Safe(const Safe&) { std::cout << "C"; } Safe(Safe&&) noexcept { std::cout << "M"; } };
struct Unsafe { Unsafe() = default; Unsafe(const Unsafe&) { std::cout << "C"; } Unsafe(Unsafe&&) { std::cout << "M"; } };
int main() {
std::vector<Safe> a(2);
a.reserve(10); // reallocation moves the 2 elements
std::cout << '\n';
std::vector<Unsafe> b(2);
b.reserve(10); // reallocation copies — the move might throw
std::cout << '\n';
}MM
CCPitfalls
std::moveonly casts; if the type has no move constructor, the copy constructor is used.Don't use a moved-from object except to assign to it or destroy it (standard types guarantee “valid but unspecified”).
return std::move(local);prevents copy elision — justreturn local;(it's moved automatically if not elided).constobjects can't be moved from:std::move(constObj)yieldsconst T&&, which binds to the copy constructor.
What is an rvalue reference?
Why should move constructors be noexcept?