progress

I'd rather be anything but ordinary

0%

The rule of cpp

The Rule of There,Five And Zero

Attention: The original artices at stackoverflow.

The Rule of There

The Rule of Three states that if a type ever needs to have a user-defined copy constructor, copy assignment operator, or destructor, then it must have all three.
The reason for the rule is that a class which needs any of the three manages some resource (file handles, dynamically allocated memory, etc), and all three are needed to manage that resource consistently. The copy functions deal with how the resource gets copied between objects, and the destructor would destroy the resource, in accord with RAII principles.

Consider a type that manages a string resource:

class Person
{
char* name;
int age;

public:
Person(char const* new_name, int new_age)
: name(new char[std::strlen(new_name) + 1])
, age(new_age)
{
std::strcpy(name, new_name);
}

~Person() {
delete [] name;
}
};

Since name was allocated in the constructor, the destructor deallocates it to avoid leaking memory. But what happens if such an object is copied?

int main()
{
Person p1("foo", 11);
Person p2 = p1;
}

First, p1 will be constructed. Then p2 will be copied from p1. However, the C++-generated copy constructor will copy each component of the type as-is. Which means that p1.name and p2.name both point to the same string.

When main ends, destructors will be called. First p2’s destructor will be called; it will delete the string. Then p1’s destructor will be called. However, the string is already deleted. Calling delete on memory that was already deleted yields undefined behavior.

To avoid this, it is necessary to provide a suitable copy constructor. One approach is to implement a reference counted system, where different Person instances share the same string data. Each time a copy is performed, the shared reference count is incremented. The destructor then decrements the reference count, only releasing the memory if the count is zero.

Or we could implement value semantics and deep copying behavior:

Person(Person const& other)
: name(new char[std::strlen(other.name) + 1])
, age(other.age)
{
std::strcpy(name, other.name);
}

Person &operator=(Person const& other)
{
// Use copy and swap idiom to implement assignment
Person copy(other);
swap(copy); // assume swap() exchanges contents of *this and copy
return *this;
}

Implementation of the copy assignment operator is complicated by the need to release an existing buffer. The copy and swap technique creates a temporary object which holds a new buffer. Swapping the contents of *this and copy gives ownership to copy of the original buffer. Destruction of copy, as the function returns, releases the buffer previously owned by *this.

The Rule of Five

C++11 introduces two new special member functions: the move constructor and the move assignment operator. For all the same reasons that you want to follow the Rule of Three in C++03, you usually want to follow the Rule of Five in C++11: If a class requires ONE of five special member functions, and if move semantics are desired, then it most likely requires ALL FIVE of them.

Note, however, that failing to follow the Rule of Five is usually not considered an error, but a missed optimisation opportunity, as long as the Rule of Three is still followed. If no move constructor or move assignment operator is available when the compiler would normally use one, it will instead use copy semantics if possible, resulting in a less efficient operation due to unnecessary copy operations. If move semantics aren’t desired for a class, then it has no need to declare a move constructor or assignment operator.

Same example as for the Rule of Three:

class Person
{
char* name;
int age;

public:
// Destructor
~Person() { delete [] name; }

// Implement Copy Semantics
Person(Person const& other)
: name(new char[std::strlen(other.name) + 1])
, age(other.age)
{
std::strcpy(name, other.name);
}

Person &operator=(Person const& other)
{
// Use copy and swap idiom to implement assignment.
Person copy(other);
swap(*this, copy);
return *this;
}

// Implement Move Semantics
// Note: It is usually best to mark move operators as noexcept
// This allows certain optimizations in the standard library
// when the class is used in a container.

Person(Person&& that) noexcept
: name(nullptr) // Set the state so we know it is undefined
, age(0)
{
swap(*this, that);
}

Person& operator=(Person&& that) noexcept
{
swap(*this, that);
return *this;
}

friend void swap(Person& lhs, Person& rhs) noexcept
{
std::swap(lhs.name, rhs.name);
std::swap(lhs.age, rhs.age);
}
};

Alternatively, both the copy and move assignment operator can be replaced with a single assignment operator, which takes an instance by value instead of reference or rvalue reference to facilitate using the copy-and-swap idiom.

Person& operator=(Person copy)
{
swap(*this, copy);
return *this;
}

Extending from the Rule of Three to the Rule of Five is important for performance reasons, but is not strictly necessary in most cases. Adding the copy constructor and assignment operator ensures that moving the type will not leak memory (move-constructing will simply fall back to copying in that case), but will be performing copies that the caller probably did not anticipate.

The Rule of Zero

We can combine the principles of the Rule of Five and RAII to get a much leaner interface: the Rule of Zero: any resource that needs to be managed should be in its own type. That type would have to follow the Rule of Five, but all users of that resource do not need to write any of the five special member functions and can simply default all of them.

Using the Person class introduced in the Rule of Three example, we can create a resource-managing object for cstrings:

class cstring {
private:
char* p;

public:
~cstring() { delete [] p; }
cstring(cstring const& );
cstring(cstring&& );
cstring& operator=(cstring const& );
cstring& operator=(cstring&& );

/* other members as appropriate */
};

And once this is separate, our Person class becomes far simpler:

class Person {
cstring name;
int arg;

public:
~Person() = default;
Person(Person const& ) = default;
Person(Person&& ) = default;
Person& operator=(Person const& ) = default;
Person& operator=(Person&& ) = default;

/* other members as appropriate */
};

The special members in Person do not even need to be declared explicitly; the compiler will default or delete them appropriately, based on the contents of Person. Therefore, the following is also an example of the rule of zero.

struct Person {
cstring name;
int arg;
};

If cstring were to be a move-only type, with a deleted copy constructor/assignment operator, then Person would automatically be move-only as well.

The term rule of zero was introduced by R. Martinho Fernandes

————这才是本体————-

上面是在stackoverflow发现的c++类编写原则,the rule of there说白了就是浅拷贝(shallow copy),深拷贝(deep copy),the rule of five添加了c++11的move constructor 和 assignment operator,the rule of zero 就是让类中的成员都有the rule of five的每个操作.