If a class needs a custom destructor, copy or move operation, it almost certainly needs all of them. Better still: design so it needs none.
The rules
Rule of Zero in practice
#include <iostream>
#include <memory>
#include <string>
#include <vector>
class Document { // no destructor, no copy/move code
std::string title_;
std::vector<std::string> lines_;
std::unique_ptr<int> cache_ = std::make_unique<int>(0); // move-only member
public:
explicit Document(std::string t) : title_(std::move(t)) {}
void add(std::string line) { lines_.push_back(std::move(line)); }
std::size_t size() const { return lines_.size(); }
};
int main() {
Document d("notes");
d.add("line 1");
Document moved = std::move(d); // compiler-generated move: correct automatically
std::cout << moved.size() << ' ' << d.size() << '\n';
// Document copy = moved; // error: unique_ptr member makes it move-only
}1 0Rule of Five, spelled out
#include <utility>
class Handle {
int* res_ = nullptr;
public:
explicit Handle(int v) : res_(new int(v)) {}
~Handle() { delete res_; } // 1 destructor
Handle(const Handle& o) : res_(o.res_ ? new int(*o.res_) : nullptr) {} // 2 copy ctor
Handle& operator=(const Handle& o) { // 3 copy assign
Handle tmp(o);
std::swap(res_, tmp.res_);
return *this;
}
Handle(Handle&& o) noexcept : res_(std::exchange(o.res_, nullptr)) {} // 4 move ctor
Handle& operator=(Handle&& o) noexcept { // 5 move assign
std::swap(res_, o.res_); // old value dies with o
return *this;
}
};What declaring one member suppresses
| You declare… | Default ctor | Copy ops | Move ops |
|---|---|---|---|
| Nothing | Generated | Generated | Generated |
| Any constructor | Not generated | Generated | Generated |
| Destructor | Generated | Generated (deprecated) | Not generated → moves become copies |
| Copy ctor or copy assignment | — | The other is generated (deprecated) | Not generated |
| Move ctor or move assignment | — | Deleted | The other is not generated |
Explain the Rule of Zero.