DMarlon Studio

Lesson 8 · 8 of 20

Copy and move · C++

Tell copying from moving in C++: the rule of five, std::move and noexcept, with an image class that manages its own memory.

C++ · Copy and move in DMarlon Studio: the example's code and its output in the terminal

Image allocates its pixels with new int[size]() and frees them in the destructor. A class that manages memory by hand needs the rule of five: if you write the destructor, also write the copy constructor, the move constructor, and copy and move assignment. Otherwise the compiler-generated copy would copy just the pointer, and both images would free the same memory.

The copy constructor, Image(const Image& o), makes a deep copy: it allocates new memory and copies every pixel. That is what happens in Image backup = photo;, and it means copying two million values.

The move constructor, Image(Image&& o), takes an rvalue reference (&&), an object that is not going to be used again. Instead of copying, it takes over the pointer and leaves the original empty but valid (nullptr and size 0). `std::move(photo)` moves nothing by itself: it only says it is fine to steal from there, and afterwards photo.size() is 0. It is marked `noexcept` so that std::vector dares to move rather than copy when it grows.

In album.push_back(Image(640 * 480)) the image is a temporary, so it is moved without asking; std::move(backup) moves a named variable too. reserve(2) stops the vector from growing halfway and moving extra elements.

Assignment uses the copy-and-swap idiom: operator=(Image o) takes its parameter by value (copied or moved, depending on what is passed) and swaps it in with std::swap; o's destructor then frees the old pixels. One function covers both assignments. In practice, if you use std::vector or smart pointers instead of new, you need none of the five: that is the rule of zero.

Code · main.cpp

#include <iostream>#include <utility>#include <vector>class Image {                 // owns a raw buffer, so it needs the rule of five    int size_;    int* pixels_;public:    explicit Image(int size) : size_(size), pixels_(new int[size]()) {}    ~Image() { delete[] pixels_; }                                          // 1 destructor    Image(const Image& o) : size_(o.size_), pixels_(new int[o.size_]) {     // 2 copy: deep        for (int i = 0; i < size_; ++i) pixels_[i] = o.pixels_[i];        std::cout << "copy " << size_ << " pixels\n";    }    Image(Image&& o) noexcept : size_(o.size_), pixels_(o.pixels_) {       // 3 move: steal        o.size_ = 0; o.pixels_ = nullptr; std::cout << "move, no copy\n";    }    Image& operator=(Image o) noexcept {      // 4 and 5: copy and move assignment        std::swap(size_, o.size_); std::swap(pixels_, o.pixels_); return *this;    }    int size() const { return size_; }};int main() {    Image photo(1920 * 1080);    Image backup = photo;                  // copy constructor    Image thumb = std::move(photo);        // move constructor: photo is left empty    std::cout << "photo " << photo.size() << ", backup " << backup.size() << ", thumb " << thumb.size() << "\n";    std::vector<Image> album; album.reserve(2);    album.push_back(Image(640 * 480));     // a temporary is moved, not copied    album.push_back(std::move(backup));    backup = thumb;                        // copy assignment: o is a copy of thumb    std::cout << "album " << album.size() << ", backup " << backup.size() << "\n";}

Output · c++ -std=c++17 main.cpp -o main && ./main

copy 2073600 pixels
move, no copy
photo 0, backup 2073600, thumb 2073600
move, no copy
move, no copy
copy 2073600 pixels
album 2, backup 2073600

Key points

  • Copying duplicates the data; moving hands over the resources and leaves the source empty.
  • T(const T&) copies and T(T&&) moves.
  • std::move(x) makes x movable; do not use it afterwards except to assign to it.
  • Mark the move constructor noexcept.
  • Rule of five if you manage resources by hand; rule of zero otherwise.

Exercise

Remove album.reserve(2); and run the program again. Count the extra move messages and explain where they come from.