C ++: Copy control of Classes

Source: Internet
Author: User

1. What is copy control of a class?

When we define a class, we usually need to consider the following items to make the class type we define as easy as the built-in type (char, int, double, etc:

Q1: use this class object to initialize another object of the same type.

Q2: Assign the class object to another object of the same type.

Q3: This class object has a lifecycle. For example, when the Code Department ends, the partial object needs to be destroyed.

Therefore, C ++ defines five copy control operations, two of which are newly added to the C ++ 11 standard:

Copy constructor)

Move constructor)

Copy-assignment operator)

Move-assignment operator)

Destructor)

When the first two constructors occur in Q1 and the two value assignment operators in Q2, The Destructor are responsible for destroying class objects.

But for beginners, it is both a good news and a disaster. If we do not define these control operators in the defined classes, the compiler will automatically synthesize a version for us. This sometimes seems to be a good thing, but the compiler is not omnipotent, and its behavior is often not what we want.

Therefore, the most difficult part in implementing copy control operations is to recognize when these operations need to be defined.

2. Copy the constructor.

A copy constructor is a constructor. Its parameters are references of its own class type. If there are other parameters, any additional parameters have default values.

class Foo{ public:     Foo();     Foo(const Foo&); };

We can notice several issues from the code above:

1. We define the form parameter as the const type. Although we can also define a non-const form parameter, this is basically meaningless, because the function only involves Member copy operations.

2. The parameter must be a reference of its own class type. Why?

We know that the function is involved in passing values between the form parameters by copying them. When we pass the object of this class to the form parameter of a function, we will call the copy constructor of this class, and the copy constructor itself is also a function, because it is a value transfer rather than reference, when calling it, you also need to call the copy constructor of the class (itself), so that the infinite loop can not be completed.

3. Copying constructors is not explicit.

If we do not define a copy constructor, the compiler will define one for us. This function will copy each non-static member from the given object to the object being created in sequence. The member's own type determines how it is copied: The member of the class type will use its copy constructor to copy; the built-in type is directly copied; array members are copied element by element.

Differentiate between direct initialization and copy initialization:

String name ("name_str"); // directly initialize string name = string ("name_str"); // copy the initialization string name = "name_str"; // copy the initialization

Direct initialization requires the compiler to use normal function matching to select the constructors that best match the provided parameters. When we use copy initialization, we require the compiler to copy the right operation object to the object being created. If necessary, type conversion is required (the third line of code hides a C-style string and converts it to the string type ).

3. Copy the value assignment operator

The copy assignment operator is an overload function for the assignment operator. It returns a reference to the operation object on the left.

class Foo { public:     Foo& operator=(const Foo&); };

Like the copy constructor, if the class is not defined with the copy assignment operator, the compiler will synthesize it.

4. destructor

A destructor is composed of a wavy concatenation class name. It does not return values or accept parameters. Because there is no parameter, there is no overload function, that is, a class has only one destructor.

The Destructor is opposite to the constructor, so let's first recall what a constructor has done:

1. Create each member in the order defined by the Member.

2. initialize each member based on the member initialization list.

3. Execute the constructor body.

The Destructor does not contain anything similar to the initialization list in the constructor to control how Members are destroyed. The Destructor is implicit. How to destroy a Member depends on its own type. If it is a class type, it calls its own destructor. If it is a built-in type, it will be destroyed automatically. If it is a pointer, You need to manually release the space pointed to by the pointer. Unlike normal pointers, a smart pointer is a class that has its own destructor.

When will the Destructor be called? When the object is destroyed:

  • The variable is destroyed when it leaves its scope;
  • When an object is destroyed, its members are destroyed.
  • When the container is destroyed, the Member is destroyed.
  • For dynamically allocated objects, the delete operator is destroyed when the delete operator is applied to the pointer to the object.
  • For a temporary object, the event expression is destroyed when it is created.

It is worth noting that destructor runs automatically. The function body of the Destructor does not directly destroy the members. The members are destroyed in the implicit structure stage after the Destructor body. During the entire object destruction process, the Destructor is performed as an additional part of the member destruction step.

5. Define the copy control operation principles

As mentioned in point 1st, when defining a class, the most difficult thing to process copy control is when you need to define the class and when the compiler needs to synthesize it by itself.

We can have the following two principles:

If a class needs to define the destructor, it is almost certain that it also needs a copy constructor and a copy assignment function, which in turn may not necessarily be true.

If a class needs a copy constructor, it is almost certain that it also needs a copy assignment function, and vice versa.

Why is the relationship between the Destructor and the copy constructor and the value assignment function so close, or why should we put the Destructor together when discussing the copy control (five types?

First, we should consider when we must define the Destructor by ourselves. For example, there is a dynamic memory allocation in the class.

class HasPtr { public:     HasPtr(const string&s = string()) :ps(new string(s), i(0)){}     ~HasPtr(){ delete ps; } private:     int i;     string* ps; };

We know that if the Destructor is automatically synthesized by the compiler, it will not delete the pointer variable, so the memory pointed to by ps will not be released, therefore, an automatically defined destructor is required. What if I didn't define the copy constructor and the copy assignment function for this class?

The automatically merged version of the compiler copies pointer members, which means that multiple HasPtr objects may point to the same memory.

HasPtr p ("some values"); f (p); // when f ends, p. the memory pointed to by ps is released HasPtr q (p); // now both p and q point to invalid memory
6. Use = default and = delete

We can use = default to explicitly require the compiler to generate a merged version. The synthesized function is implicitly declared as inline. If we do not want the Members to be merged to be inline, we should only use = default for the member's out-of-class definition.

Sometimes some classes we define do not need to copy constructors or copy assignment operators. For example, the iostream class blocks copying to avoid multiple objects from writing or reading the same IO buffer.

In the new standard, we can add = delete to the parameter list of the copy constructor and the copy assignment operator to indicate that we want to define it as deleted, such a function is called a delete function.

Class NoCopy {NoCopy () = default; // use the default constructor NoCopy (const NoCopy &) = delete; // delete copy NoCopy & operator = (const NoCopy &) = delete; // delete a value ~ NoCopy () = default; // use the compositing destructor };

Note: The Destructor cannot be a deleted member because such classes cannot be destroyed.

If a class has a const member or a reference member, the copy assignment operator of this class is defined as Delete.

Before the new standard comes out, the class blocks the copy by declaring the copy assignment operator of the copy constructor as private, and to prevent the Member from being accessed by friends or other members, only these member functions are declared, but not defined.

7. Right reference

The so-called right value reference is a reference that must be bound to the right value. We can use & to obtain the right value reference, A very important property of the right value reference is that it can only be bound to one object to be destroyed, so we can freely "move" one resource referenced by the right value to another object.

We can bind a right value reference to an expression, but cannot bind the right value reference to a left value:

Int I = 42; int & r = I; // correct: r references I int & rr = I; // error: A reference to the right value cannot be bound to an int & r2 = I * 42; // I * 42 is a right value const int & r3 = I * 42; // You can bind a const reference to an int & rr2 = I * 42; // correct: bind rr2 to the multiplication result

In general, the left value has a persistent state, and the right value is either a literal constant or a temporary object created during expression evaluation.

As a result, we know that the right value reference: 1) the referenced object will be destroyed; 2) the object has no other users.

The standard library provides a std: move function, so that we can get the reference of the right value on the left value:

Int & r3 = std: move (rr1); // rr1 is a variable.

The move call tells the compiler that we have a left value, but we want to process it like a right value. After the code above, either rr1 is destroyed or rr1 is assigned a value. Otherwise, rr1 cannot be used.

It is worth noting that we use std: move instead of move, even if we provide the using declaration.

8. Mobile constructor and mobile assignment operator

Like copying, a mobile operation occurs when the objects of the same class are initialized or assigned values to objects of the same class type, but unlike copying, the content of the object is actually moved from the source object to the target object, and the content of the source object is lost. Generally, a move operation occurs only when the source object is an uname object.

An uname object is a temporary object and has not been assigned a name. For example, an object returned by a function of this type or a type conversion operation.

MyClass fn();            // function returning a MyClass objectMyClass foo;             // default constructorMyClass bar = foo;       // copy constructorMyClass baz = fn();      // move constructorfoo = bar;               // copy assignmentbaz = MyClass();         // move assignment 

In the above Code, the objects returned by fn () and the objects constructed by MyClass are all unnamed. When such objects are assigned or initialized to MyClass, they do not need to be copied, because the source object only has a short lifecycle.

The definition of the mobile constructor and the mobile assignment function is the same as that of the copy operation. It only replaces the reference of the copy function's form parameter with the reference of the right value.

MyClass (MyClass&&);             // move-constructorMyClass& operator= (MyClass&&);  // move-assignment

Mobile operations are very useful for classes that need to manage buckets. For example, the class defined below

// move constructor/assignment#include <iostream>#include <string>using namespace std;class Example6 {    string* ptr;  public:    Example6 (const string& str) : ptr(new string(str)) {}    ~Example6 () {delete ptr;}    // move constructor    Example6 (Example6&& x) : ptr(x.ptr) {x.ptr=nullptr;}    // move assignment    Example6& operator= (Example6&& x) {      delete ptr;       ptr = x.ptr;      x.ptr=nullptr;      return *this;    }    // access content:    const string& content() const {return *ptr;}    // addition:    Example6 operator+(const Example6& rhs) {      return Example6(content()+rhs.content());    }};int main () {  Example6 foo ("Exam");  Example6 bar = Example6("ple");   // move-construction    foo = foo + bar;                  // move-assignment  cout << "foo's content: " << foo.content() << '\n';  return 0;}

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.