C++ Class Assignment Operator Destructor

[edit]Rule of three

If a class requires a user-defined destructor, a user-defined copy constructor, or a user-defined copy assignment operator, it almost certainly requires all three.

Because C++ copies and copy-assigns objects of user-defined types in various situations (passing/returning by value, manipulating a container, etc), these special member functions will be called, if accessible, and if they are not user-defined, they are implicitly-defined by the compiler.

The implicitly-defined special member functions are typically incorrect if the class is managing a resource whose handle is an object of non-class type (raw pointer, POSIX file descriptor, etc), whose destructor does nothing and copy constructor/assignment operator performs a "shallow copy" (copy the value of the handle, without duplicating the underlying resource).

Classes that manage non-copyable resources through copyable handles may have to declare copy assignment and copy constructor private and not provide their definitions or define them as deleted. This is another application of the rule of three: deleting one and leaving the other to be implicitly-defined will most likely result in errors.

[edit]Rule of five

Because the presence of a user-defined destructor, copy-constructor, or copy-assignment operator prevents implicit definition of the move constructor and the move assignment operator, any class for which move semantics are desirable, has to declare all five special member functions:

Unlike Rule of Three, failing to provide move constructor and move assignment is usually not an error, but a missed optimization opportunity.

[edit]Rule of zero

Classes that have custom destructors, copy/move constructors or copy/move assignment operators should deal exclusively with ownership (which follows from the Single Responsibility Principle). Other classes should not have custom destructors, copy/move constructors or copy/move assignment operators.[1]

When a base class is intended for polymorphic use, its destructor may have to be declared public and virtual. This blocks implicit moves (and deprecates implicit copies), and so the special member functions have to be declared as defaulted[2]

however, this can be avoided if the objects of the derived class are not dynamically allocated, or are dynamically allocated only to be stored in a std::shared_ptr (such as by std::make_shared): shared pointers invoke the derived class destructor even after casting to .

[edit]References

  1. ↑"Rule of Zero", R. Martinho Fernandes 8/15/2012
  2. ↑"A Concern about the Rule of Zero", Scott Meyers, 3/13/2014
class rule_of_three {char* cstring;// raw pointer used as a handle to a dynamically-allocated memory blockpublic: rule_of_three(constchar* arg): cstring(new char[std::strlen(arg)+1])// allocate{std::strcpy(cstring, arg);// populate} ~rule_of_three(){ delete[] cstring;// deallocate} rule_of_three(const rule_of_three& other)// copy constructor{ cstring = new char[std::strlen(other.cstring)+1];std::strcpy(cstring, other.cstring);} rule_of_three& operator=(const rule_of_three& other)// copy assignment{char* tmp_cstring = new char[std::strlen(other.cstring)+1];std::strcpy(tmp_cstring, other.cstring); delete[] cstring; cstring = tmp_cstring;return*this;}// alternatively, reuse destructor and copy ctor// rule_of_three& operator=(rule_of_three other)// {// std::swap(cstring, other.cstring);// return *this;// }};
class rule_of_five {char* cstring;// raw pointer used as a handle to a dynamically-allocated memory blockpublic: rule_of_five(constchar* arg): cstring(new char[std::strlen(arg)+1])// allocate{std::strcpy(cstring, arg);// populate} ~rule_of_five(){ delete[] cstring;// deallocate} rule_of_five(const rule_of_five& other)// copy constructor{ cstring = new char[std::strlen(other.cstring)+1];std::strcpy(cstring, other.cstring);} rule_of_five(rule_of_five&& other): cstring(other.cstring)// move constructor{ other.cstring= nullptr;} rule_of_five& operator=(const rule_of_five& other)// copy assignment{char* tmp_cstring = new char[std::strlen(other.cstring)+1];std::strcpy(tmp_cstring, other.cstring); delete[] cstring; cstring = tmp_cstring;return*this;} rule_of_five& operator=(rule_of_five&& other)// move assignment{if(this!=&other)// prevent self-move{ delete[] cstring; cstring = other.cstring; other.cstring= nullptr;}return*this;}// alternatively, replace both assignment operators with // rule_of_five& operator=(rule_of_five other)// {// std::swap(cstring, other.cstring);// return *this;// }};
class rule_of_zero {std::string cppstring;public: rule_of_zero(conststd::string& arg): cppstring(arg){}};
class base_of_five_defaults {public: base_of_five_defaults(const base_of_five_defaults&)=default; base_of_five_defaults(base_of_five_defaults&&)=default; base_of_five_defaults& operator=(const base_of_five_defaults&)=default; base_of_five_defaults& operator=(base_of_five_defaults&&)=default;virtual ~base_of_five_defaults()=default;};

The rule of three and rule of five are rules of thumb in C++ for the building of exception-safe code and for formalizing rules on resource management. It accomplishes this by prescribing how the default members of a class should be used to accomplish this task in a systematic manner.

Rule of Three[edit]

The rule of three (also known as the Law of The Big Three or The Big Three) is a rule of thumb in C++ (prior to C++11) that claims that if a classdefines one (or more) of the following it should probably explicitly define all three:[1]

These three functions are special member functions. If one of these functions is used without first being declared by the programmer it will be implicitly implemented by the compiler with the following default semantics:

  • 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.

The Rule of Three claims that if one of these had to be defined by the programmer, it means that the compiler-generated version does not fit the needs of the class in one case and it will probably not fit in the other cases either. The term "Rule of three" was coined by Marshall Cline in 1991.[2]

An amendment to this rule is that if the class is designed in such a way that Resource Acquisition Is Initialization (RAII) is used for all its (nontrivial) members, the destructor may be left undefined (also known as The Law of The Big Two[3]). A ready-to-go example of this approach is the use of smart pointers instead of plain ones. [3]

Because implicitly-generated constructors and assignment operators simply copy all class data members ("shallow copy"),[4] one should define explicit copy constructors and copy assignment operators for classes that encapsulate complex data structures or have external references such as pointers, if you need to copy the objects pointed to by the class members. If the default behavior ("shallow copy") is actually the intended one, then an explicit definition, although redundant, will be a "self-documenting code" indicating that it was an intention rather than an oversight.

Rule of Five[edit]

With the advent of C++11 the rule of three can be broadened to the rule of five as C++11 implements move semantics,[5] allowing destination objects to grab (or steal) data from temporary objects. The following example also shows the new moving members: move constructor and move assignment operator. Consequently, for the rule of five we have the following special members:

Situations exist where classes may need destructors, but cannot sensibly implement copy and move constructors and copy and move assignment operators. This happens, for example, when the base class does not support these latter Big Four members, but the derived class's constructor allocates memory for its own use.[citation needed] In C++11, this can be simplified by explicitly specifying the five members as default.[6]

Example in C++[edit]

#include<cstring>#include<iostream>classFoo{public:/** Default constructor */Foo():data(newchar[14]){std::strcpy(data,"Hello, World!");}/** Copy constructor */Foo(constFoo&other):data(newchar[std::strlen(other.data)+1]){std::strcpy(data,other.data);}/** Move constructor */Foo(Foo&&other)noexcept:/* noexcept needed to enable optimizations in containers */data(other.data){other.data=nullptr;}/** Destructor */~Foo()noexcept/* explicitly specified destructors should be annotated noexcept as best-practice */{delete[]data;}/** Copy assignment operator */Foo&operator=(constFoo&other){Footmp(other);// re-use copy-constructor*this=std::move(tmp);// re-use move-assignmentreturn*this;}/** Move assignment operator */Foo&operator=(Foo&&other)noexcept{if(this==&other){// take precautions against `foo = std::move(foo)`return*this;}delete[]data;data=other.data;other.data=nullptr;return*this;}private:friendstd::ostream&operator<<(std::ostream&os,constFoo&foo){os<<foo.data;returnos;}char*data;};intmain(){constFoofoo;std::cout<<foo<<std::endl;return0;}

See also[edit]

References[edit]

0 thoughts on “C++ Class Assignment Operator Destructor”

    -->

Leave a Comment

Your email address will not be published. Required fields are marked *