Rule of Three
The rule of three is a rule of thumb in C++ (prior to C++11) that claims that if a class defines any of the following then it should probably explicitly define all three:
Destructor – call the destructors of all the object’s class-type members
Copy constructor – construct all the object’s members from the corresponding members of the copy constructor’s argument, calling the copy constructors of the object’s class-type members, and doing a plain assignment of all non-class type (e.g., int or pointer) data members
Copy assignment operator – assign all the object’s members from the corresponding members of the assignment operator’s argument, calling the copy assignment operators of the object’s class-type members, and doing a plain assignment of all non-class type (e.g. int or pointer) data members.
Rule of Five
With the advent of C++11 the rule of three can be broadened to the rule of five.
C++11 implements move semantics, allowing destination objects to grab (or steal) data from temporary objects.
For the rule of five we have the following special members:
Example for Linked List
Taken from CS247.
Declaration (Node.h
)
#ifndef NODE_H
#define NODE_H
#include <string>
struct Node {
std::string data;
Node* next;
Node(const std::string& data, Node* next); // Ctor
// Big 5
Node(const Node& other); // Copy Ctor
Node(Node&& other); // Move Ctor
~Node(); // Dtor
Node& operator=(const Node& other); // Copy assignment operator
Node& operator=(Node&& other); // Move assignment operator
};
#endif
- Notice that
const
is everywhere except for the move operators, because by nature you will be moving it
Definition Node.cc
#include "Node.h"
#include <utility>
Node::Node(const std::string& data, Node* next): data{data}, next{next} {}
Node::Node(const Node& other): data{other.data}, next{other.next ? new Node{*(other.next)} : nullptr} {}
Node::Node(Node&& other): data{std::move(other.data)}, next{other.next} {
other.next = nullptr;
}
Node::~Node() {
delete next;
}
Node& Node::operator=(const Node& other) {
Node tmp{other};
std::swap(data, tmp.data);
std::swap(next, tmp.next);
return *this;
}
Node& Node::operator=(Node&& other) {
std::swap(data, other.data);
std::swap(next, other.next);
return *this;
}
Usage main.cc
#include "Node.h"
Node getAlphabet() {
return Node{"a", new Node{"b", new Node{"c", nullptr}}};
}
int main() {
Node n{"1", new Node{"2", new Node{"3", nullptr}}};
Node p{n}; // Copy ctor
Node alphabet{getAlphabet()}; // Move ctor
n = p; // Copy assignment operator
n = getAlphabet(); // Move assignment operator
}
With Polymorphism, it gets a lot more complicated. See Polymorphic Big Five.
Rule of Zero
The basic idea here is that if all of the class’s fields are non-pointers from the C++ standard library and/or are C++ default types (int, bool, char, …), then the compiler will automatically generate The Big Three for you.
The compiler will always generate The Big Three for you if you don’t write them yourself.