๐ŸŽ“ A1Learner
๐Ÿ”
0 of 28 lessons completed

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?