In C++, many important methods are automatically generated by the compiler (like the default constructor, destructor, and copy constructor). This means that the programmer doesn’t need to re-define these methods.
However, there are a few cases where we may have to implement our own methods. The rule of three states that, if a class requires a user-defined implementation of any one of the following, then it often requires all three.1
- Copy constructor
- Destructor
- operator=, i.e., the copy assignment operator
The main reason we may need to implement one of these is if the class manages a dynamically allocated resource via a raw pointer or file descriptor. In these cases, the compiler-generated defaults will fail to manage these properly, which will cause a resource leak.
In cases where we use move semantics, the rule of five suggests that we also implement the move constructor and move assignment operator. In the inverse case, if all of the class’ fields aren’t explicitly managed by the object, then the compiler-generated three fields will suffice. This is called the rule of zero.
Example
Take the example below.
- The copy constructor is defined such that it allocates a new buffer to store an input string.
- Because we allocated new memory, we must ensure that when
MyStringis deleted, we also free the allocated memory in the destructor. - In the
operator=case, the implicit copy assignment operator will naïvely “copy by value” for the other object’s fields. Instead of doing a “deep copy” of the values in the buffer, it just copies the pointer that the other object stores.- Think about how this can be bad if we didn’t implement
operator=ourselves. The LHS object would hold a pointer to the RHS’ buffer. If the RHS object is deleted, then the LHS object may access invalid memory.
- Think about how this can be bad if we didn’t implement
// copy constructor
MyString::MyString (const MyString& x) {
len = x.len;
buf = new char[len + 1];
strcpy(buf, x);
}
// destructor
MyString::~MyString() {
if (buf != nullptr) {
delete [] buf;
buf = nullptr;
}
}
// assignment operator
MyString& MyString::operator=(const MyString& src) {
delete [] buf;
buf = new char[strlen(src) + 1]
strcpy(buf, src.buf);
len = src.len;
return *this;
}