Special Member Functions in C++
In C++, an object's lifecycle semantics are implemented by the so-called special member functions; i.e., construction, copy, move, and destruction. In certain cases, the compiler may implicitly define a default member function, or a user-defined (explicit) special member function may prevent the generation of compiler defined (default) function. Actually, it is a complicated relationship between explicit and implicit definitions. That's why the C++ Core Guidelines advice to define either none (rule of zero) or all (rule of five) of the special member functions.
= delete and = default are considered as explicit definitions.class Simple {
public:
Simple(const Simple&) = default; // copy constructor
Simple& operator=(const Simple&) = default; // copy assignment
Simple(Simple&&) noexcept = default; // move constructor
Simple& operator=(Simple&&) noexcept = default; // move assignment
~Simple() = default; // destructor
// ...
};implicit copy issue
Declaring class destructor results in implicit copy semantics to be defaulted, which prevents the implicit move operations. The following example demonstrates two different cases where Copy class with only destructor, and Move class with destructor and move. In the case for Copy class, the compiler will default to generate copy special functions. In order to enable move semantics, Move class declares an explicit move assignment operator.
struct Base {
int value {0};
};
struct Copy: Base {
~Copy() { } // implicit copy
};
struct Move: Base {
~Move() { } // implicit copy
Move& operator=(Move&&) = default; // user-defined move assignment
};
Copy cA, cB;
cB = std::move(cA); // cA is copied
Move mA, mB;
mB = std::move(mA); // mA is movedCanonical Operators
The C++ language provides quite a lot of built-in operators. There is no restriction on what the overloaded operators do (or what type they return), but there are some common knowledge expectations. For example, the assignment operators are expected to return by reference in order to allow x = y = z statements.
Assignment Operators: operator=
The two main semantics are copy and move. One may consider copy-and-swap if makes sense. Both copy and move assignment operators should be guarded against self-assignment, and return by reference. The move assignment should also be noexcept, and leave the moved-from (source) object in a valid (unspecified) state.
move assignment
The recommendation is to use = default whenever possible. If the class has non-smart members, such as raw pointers, then one should define the move assignment. It is important to set noexcept, guard against self-assignment, and leave the moved-from object in a valid state. An example below shows such a case.
#include <memory>
#include <utility>
class Simple {
public:
// ...
Simple& operator=(Simple&& other) noexcept {
// self-assignment guard
if (this == &other) { return *this; }
// cleanup resources
delete[] indexes;
// set nullptr as valid state
indexes = std::exchange(other.indexes, nullptr);
// unique_ptr is movable
left = std::move(other.left);
right = std::move(other.right);
// return by reference
return *this;
}
private:
int* indexes {nullptr};
std::unique_ptr<Simple> left;
std::unique_ptr<Simple> right;
};copy-and-swap idiom
This is useful in special cases. It takes other by value, swaps its resources, and releases old resources via destructor of other. See example below:
Simple& Simple::operator=(Simple other) noexcept {
data_.swap(other.data_);
index_.swap(other.index_);
return *this;
}Move Semantics
Most of the time, compilers copy values because they cannot tell if a value is no longer needed.
std::move() for no longer needed objects.