Intermediate · Classes, constructors and RAII · topic 16 of 42 · 30 min
Copying objects: the rule of three, five and zero
Explanation
The special member functions
Besides constructors and the destructor, C++ can generate four more special functions for a class: the copy constructor (make a new object as a copy), the copy assignment operator (overwrite an existing object with a copy), and their move versions (move constructor and move assignment), which you will meet in the move semantics topic.
Student a("Asha", 1);
Student b = a; // copy constructor: b is a new copy of a
Student c("Ravi", 2);
c = a; // copy assignment: c already existed, now it becomes a copy of a
void enrol(Student s);
enrol(a); // copy constructor again: pass by value makes a copyShallow copy: the raw pointer disaster
class Buffer {
public:
explicit Buffer(int n) : size_(n), data_(new int[n]{}) {}
~Buffer() { delete[] data_; }
private:
int size_;
int* data_;
};
void oops() {
Buffer a(100);
Buffer b = a; // default copy copies the POINTER, not the 100 ints
} // b's destructor deletes the array, then a's destructor deletes it AGAIN: crashThe rule of three
If your class needs a custom destructor (because it owns a resource), it almost certainly also needs a custom copy constructor and copy assignment operator. These three go together: that is the rule of three. In C++11 and later, add the move constructor and move assignment and it becomes the rule of five.
class Buffer {
public:
explicit Buffer(int n) : size_(n), data_(new int[n]{}) {}
~Buffer() { delete[] data_; }
Buffer(const Buffer& other) // deep copy
: size_(other.size_), data_(new int[other.size_]) {
std::copy(other.data_, other.data_ + size_, data_);
}
Buffer& operator=(const Buffer& other) { // copy-and-swap would also work
if (this != &other) {
int* fresh = new int[other.size_];
std::copy(other.data_, other.data_ + other.size_, fresh);
delete[] data_;
data_ = fresh;
size_ = other.size_;
}
return *this;
}
private:
int size_;
int* data_;
};The rule of zero (what you should usually do)
#include <vector>
class Buffer {
public:
explicit Buffer(int n) : data_(n) {}
int size() const { return static_cast<int>(data_.size()); }
private:
std::vector<int> data_; // vector already knows how to copy, move and free itself
};The rule of zero: design classes so they need none of the five special functions, by building them from members that already handle their own resources (std::string, std::vector, std::unique_ptr, std::shared_ptr). Only low-level "resource handle" classes, like the File class in the RAII topic, should write them.
= default and = delete
- = default asks the compiler to generate the normal version, for example Buffer(const Buffer&) = default;.
- = delete forbids an operation: Buffer(const Buffer&) = delete; makes copying a compile error. Use it for things that must have one owner (files, sockets, mutexes).
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Copying objects: the rule of three, five and zero · 1/6
Copying objects looks simple, but it hides a classic C plus plus bug. Let us see it and fix it.
Prefer a video by a teacher? Find free videos on “c++ rule of three rule of five rule of zero copy constructor deep copy” on YouTube ↗
In real life: Copying a photo in an image editor
A mobile photo editor keeps each image's pixels on the heap. When you tap "duplicate layer", the program must make a deep copy, so that editing the duplicate does not change the original. Storing pixels in a std::vector<std::uint8_t> instead of a raw pointer gives correct deep copies automatically.
#include <cstdint>
#include <iostream>
#include <vector>
class Image {
public:
Image(int w, int h) : w_(w), h_(h), pixels_(static_cast<std::size_t>(w) * h, 0) {}
void brighten(std::uint8_t by) { for (auto& p : pixels_) p = static_cast<std::uint8_t>(p + by); }
int firstPixel() const { return pixels_[0]; }
private:
int w_, h_;
std::vector<std::uint8_t> pixels_;
};
int main() {
Image original(1920, 1080);
Image layer = original; // deep copy, thanks to vector
layer.brighten(40);
std::cout << original.firstPixel() << ' ' << layer.firstPixel() << '\n'; // 0 40
}Practice (0/4 done)
Student b = a; calls which function?
A class has int* data_ (allocated with new[]) and a destructor that calls delete[] data_, but no copy constructor. What happens when an object is copied and both go out of scope?
Which class follows the rule of zero?
Rewrite this class using the rule of zero, then explain in two sentences why the original crashes when copied: class Playlist { public: Playlist() : songs_(new std::string[50]) {} ~Playlist() { delete[] songs_; } private: std::string* songs_; };
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Smart pointers 1: std::unique_ptrTemplates, smart pointers and move semantics
- Move semantics: steal instead of copyTemplates, smart pointers and move semantics
- Smart pointers 2: std::shared_ptr and std::weak_ptrTemplates, smart pointers and move semantics
- C++ interview questions and how to answer themCompetitive programming, interviews and careers
- Arrays, std::array and a first look at std::vectorReferences, pointers, arrays and strings
- References, copies and equalityJavaScript Programming (language in depth) · Arrays and objects
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What does the rule of three say?
Answer: If a class needs a custom destructor, copy constructor or copy assignment, it probably needs all three
These three manage the same resource, so they come as a set.
What does Socket(const Socket&) = delete; do?
Answer: Makes copying a Socket a compile error
= delete removes the operation, so any attempt to copy fails to compile.
Why must copy assignment check this != &other?
Answer: So that a = a does not free its own data before copying from it
Self-assignment with delete-then-copy reads freed memory.
Which pair makes a deep copy automatically when you copy the object?
Answer: A std::vector member
std::vector copies all its elements into new memory.
How do you pass ownership of a unique_ptr into a function sink(std::unique_ptr<T> p)?Related
You wrote Student(std::string name, int roll). What happens with Student s;?Related
Answer: Compile error: no default constructor exists
Writing any constructor stops the compiler from generating the default one.
A class declares only a destructor (~T() {}) and holds a std::vector. What happens to its moves?Related
Answer: The implicit move operations are not declared, so moves silently become copies
A user-declared destructor suppresses the implicit move constructor and move assignment.
Why prefer std::make_shared over std::shared_ptr<T>(new T)?Related
Answer: One allocation for object and control block, and no raw new in your code
make_shared is faster and safer.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- This is the last topic of “Classes, constructors and RAII”. The module exam comes next.
- Module 4 exam: Classes, constructors and RAII · 12 questions · 20 min, without help. 4 topics of this module still to finish first.
- Then the next module, Operator overloading, inheritance and polymorphism. 42 topics left in the course before the final exam.