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.

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 andT(T&&)moves.std::move(x)makesxmovable; 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.