C++ Move-Only Types
A move-only type is a class that can be moved but never copied. Its copy operations are deleted, so the compiler refuses every copy.
A copy that shares one value
The copy constructor copies the pointer itself. Both objects now point at the same int, and a write through the copy changes what the first object reads.
#include <iostream>
class Shared {
public:
int* data;
Shared(int value) {
data = new int(value);
}
Shared(const Shared& other) {
data = other.data;
}
};
int main() {
Shared first(5);
Shared copy = first;
std::cout << "Copy reads: " << *copy.data << "\n";
*copy.data = 9;
std::cout << "First reads: " << *first.data << "\n";
return 0;
}A class that forbids copying
Handle deletes its copy operations and writes a move constructor instead. The move takes the value and sets the source to 0 by hand.
#include <iostream>
#include <utility>
class Handle {
public:
Handle(int value) : value_(value) {}
Handle(const Handle& other) = delete;
Handle& operator=(const Handle& other) = delete;
Handle(Handle&& other) : value_(other.value_) {
other.value_ = 0;
}
Handle& operator=(Handle&& other) {
if (this != &other) {
value_ = other.value_;
other.value_ = 0;
}
return *this;
}
int value() const {
return value_;
}
private:
int value_;
};
int main() {
Handle first(42);
Handle second = std::move(first);
std::cout << "First: " << first.value() << "\n";
std::cout << "Second: " << second.value() << "\n";
return 0;
}Copying a move-only type fails
The program tries to copy a Handle. The compiler refuses the copy and names the deleted copy constructor.
#include <iostream>
#include <utility>
class Handle {
public:
Handle(int value) : value_(value) {}
Handle(const Handle& other) = delete;
Handle& operator=(const Handle& other) = delete;
Handle(Handle&& other) : value_(other.value_) {
other.value_ = 0;
}
Handle& operator=(Handle&& other) {
if (this != &other) {
value_ = other.value_;
other.value_ = 0;
}
return *this;
}
int value() const {
return value_;
}
private:
int value_;
};
int main() {
Handle first(42);
Handle copy = first;
std::cout << "Copy: " << copy.value() << "\n";
return 0;
}Passing a move-only object by value
The show function takes its Handle by value. The call hands the object over with std::move, and the caller prints its empty state after the call.
#include <iostream>
#include <utility>
class Handle {
public:
Handle(int value) : value_(value) {}
Handle(const Handle& other) = delete;
Handle& operator=(const Handle& other) = delete;
Handle(Handle&& other) : value_(other.value_) {
other.value_ = 0;
}
Handle& operator=(Handle&& other) {
if (this != &other) {
value_ = other.value_;
other.value_ = 0;
}
return *this;
}
int value() const {
return value_;
}
private:
int value_;
};
void show(Handle h) {
std::cout << "Inside: " << h.value() << "\n";
}
int main() {
Handle mine(7);
show(std::move(mine));
std::cout << "After: " << mine.value() << "\n";
return 0;
}Example
#include <iostream>
#include <utility>
class Ticket {
public:
Ticket(int seat) : seat_(seat) {}
Ticket(const Ticket& other) = delete;
Ticket& operator=(const Ticket& other) = delete;
Ticket(Ticket&& other) : seat_(other.seat_) {
other.seat_ = 0;
}
Ticket& operator=(Ticket&& other) {
if (this != &other) {
seat_ = other.seat_;
other.seat_ = 0;
}
return *this;
}
int seat() const {
return seat_;
}
private:
int seat_;
};
Ticket checkIn(Ticket t) {
std::cout << "Checked seat: " << t.seat() << "\n";
return std::move(t);
}
Ticket board(Ticket t) {
std::cout << "Boarded seat: " << t.seat() << "\n";
return std::move(t);
}
int main() {
Ticket ticket(12);
Ticket kept = board(checkIn(std::move(ticket)));
std::cout << "Kept seat: " << kept.seat() << "\n";
std::cout << "Original seat: " << ticket.seat() << "\n";
return 0;
}Key points
- A class that owns a resource can forbid copying by deleting its copy operations.
- The copy constructor and copy assignment are removed by writing = delete after each one.
- The move constructor takes the stored value from the source object.
- The move constructor sets the source value to 0 by hand, and 0 is the empty state of this class.
- A move-only object can be passed by value when the call hands it over with std::move.
- The compiler refuses any copy of a move-only type and names the deleted function in its message.
In this example, the ticket moves through two functions. Each function takes the ticket by value and returns it by a move. The kept ticket prints seat 12, and the original prints seat 0.
Note
std::unique_ptr is a move-only type, so it can be moved into a function but never copied.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
What happens when a program tries to copy a move-only object?