Copying, assigning, destroying and moving classes

Copying, assigning, destroying and moving classes

The copy and move constructor defines what to do when this object is initialized by another object of the same type

The copy and move assignment operators define what is done when one object is assigned to another object of the same type

Copy constructor

Copy constructor: If the first parameter of a constructor is a reference to its own class type, and any additional parameters have default values, then this constructor is a copy constructor

(The reference is used because, if there is no reference, the formal parameter will copy the actual parameter when the copy constructor is called, but the copy constructor must be called to copy the actual parameter, and the infinite loop)

class Foo {
    public:
    Foo(); //default constructor
    Foo(const Foo & amp;); //copy constructor
    
};

Unlike synthesizing a default constructor, the compiler will synthesize a copy constructor for us even if other constructors are defined.

In general, a synthetic copy constructor will copy the members of its arguments into the object being created.

Copy:

  • A class type member, copied using its copy constructor
  • Built-in type members: direct copy
  • Array: copy element by element

?

Copy initialization

string dots(10, '.'); //Direct initialization
string s(dots); //direct initialization
string s = dots; //copy initialization
string null_book = "999999"; //copy initialization
string nines = string(100, '9'); //copy initialization

Direct initialization: asks the compiler to use normal function matching to choose the constructor that best matches the supplied parameters

Copy-initialization: Asks the compiler to copy the right-hand operand into the object being created, possibly performing a type conversion if necessary.

Copy initialization is usually done using a copy constructor

What happens when copy initialization

  • Use = to define variables
  • passing an object as an actual parameter to a non-reference type parameter
  • returns an object from a function whose return type is not a reference type
  • Initialize elements of an array or members of an aggregate class with a curly-brace list

Copy assignment operator

Composite copy assignment operator: Assigns each non-static member of the right operand to the corresponding member of the left operand.

The synthetic copy assignment operator returns a reference to its left operand.

Destructor

The name consists of a tilde followed by the class name.

Has no return value and does not receive parameters, so it cannot be overloaded, the only one.

The destructor frees the resources used by the object and destroys the object’s non-static members

The destructor first executes the function body, then destroys the members, and destroys them in the reverse order of the initialization order. Destroying a class type member calls the member’s own destructor, and destroying a built-in type requires no action.

Destroying a built-in pointer type member does not delete the object it points to. Smart pointers have destructors, so are automatically destroyed

Rule of three/five

A class that needs a destructor also needs a copy constructor and a copy assignment operator

The main reason is that the synthetic destructor will not delete a pointer data member

Prevent copying

Old Standard: Declare copy constructor and copy assignment operator as private

New standard: =delete

Object movement

Rvalue reference

https://blog.csdn.net/Appleeatingboy/article/details/129811772

Rvalue reference: A reference that must be bound to an rvalue, and can only be bound to an object that will be destroyed, obtained through & amp; & amp;

In general, an lvalue expression represents the identity of an object, and an rvalue expression represents the value of an object

Regular references can be called lvalue references. It cannot be bound to an expression requiring conversion, a literal constant, or an expression returning an rvalue.

Rvalue references are the opposite: you can bind an rvalue reference to such an expression, but you cannot bind an rvalue reference to an lvalue.

int i = 42;
int & r = i;
int & amp; & amp;r1 = i; // error, cannot bind an rvalue reference to an lvalue
int & amp; r2 = i * 42; // error, i * 42 is an rvalue
const int & amp;r3 = i * 42; //Correct, you can bind a const reference to an rvalue
int & amp; & amp;rr2 = i * 42; //correct

Standard library move function

https://www.zhihu.com/question/64205844/answer/2401017464

std::move is transferring the state or ownership of an object from one object to another, just a transfer, no memory relocation or memory copying.

call move to obtain an rvalue reference bound to an lvalue

int & amp; & amp;rr1 = 42; //correct: literal constants are rvalues
int & amp; & amp; rr2 = rr1; // error, rr1 is an lvalue
int & amp; & amp;rr3 = std::move(rr1); //ok

A shifted source object can be destroyed, and a new value can be assigned to it, but the value of a shifted source object cannot be used

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-XFYEwQwM-1684891997879)(C:/Users/87242/AppData/Roaming/Typora/typora-user-images/ image-20230511094650816.png)]

Move assignment operator

A move assignment operator is a compound assignment operator used to move the value of one variable into another variable. In C++ and many other programming languages, the move assignment operator is implemented using the rvalue reference syntax ( & amp; & amp; ) to transfer ownership of a resource without copying the object.

For example, in C++, a simple move assignment operator might look like this:

Copy Codeclass MyClass {
public:
    // move constructor
    MyClass(MyClass & amp; & amp; other) {
        // transfer resource ownership
        data_ = other. data_;
        other.data_ = nullptr;
    }
    
    // move assignment operator
    MyClass & amp; operator=(MyClass & amp; & amp; other) {
        if (this != &other) {
            // Release the current resource
            delete data_;
            
            // transfer resource ownership
            data_ = other. data_;
            other.data_ = nullptr;
        }
        
        return *this;
    }
    
private:
    int* data_;
};

In this example, the move assignment operator takes an rvalue reference argument and transfers the value of its internal pointer data member into the current object, while leaving the original object in an invalid state. In this way, expensive resource copy operations through copy constructors or copy assignment operators can be avoided, thereby improving program performance and efficiency.

But isn’t “=” used in the function body of your move assignment operator? Shouldn’t this mean copy? Why is it mobile?

Indeed, the “=” symbol appears in the function body of the move assignment operator, but the assignment here is not a copy operation, but transfers the resource ownership of the object on the right to the object on the left. This process is called move semantics.

Before C++11, we can only use the traditional copy constructor and assignment operator to copy objects, which is very inefficient for large objects and dynamically allocated objects. By introducing move semantics, we can avoid expensive memory copy operations and improve program performance.

Specifically, the move assignment operator takes an rvalue reference parameter and converts it to an rvalue using the std::move() function, allowing its internal member pointers to directly point to the memory space of the original object without copy. At the same time, since the data member of the right object is already invalid, it can be set to nullptr or other appropriate invalid state to avoid releasing duplicate resources.

In short, the move assignment operator is an efficient resource management method, which can quickly transfer the resource ownership of an object to other objects, thereby avoiding expensive copy operations.

Then how do I judge whether the “=” symbol that appears is copy or move semantics?

In C++11, we can use the special rvalue reference symbol (& amp; & amp;) and the std::move() function to determine whether an assignment operation is a copy or a move.

Specifically, if a class defines a move constructor and a move assignment operator, and uses rvalue reference symbols ( & amp; & amp; ) in these functions, then we can use std::move() function converts an object to an rvalue reference, thus triggering its move semantics. For example:

Copy CodeMyClass a;
MyClass b(std::move(a)); // use move constructor

MyClass c;
c = std::move(b); // use move assignment operator

In the above code, the std::move() function converts the objects a and b into rvalue references respectively, thus triggering the move constructor and move assignment operator. If the std::move() function is not used, the copy constructor and copy assignment operator will be called at runtime, which will cause additional performance overhead.

In summary, in C++11, we can explicitly trigger move semantics by using the rvalue reference symbol and the std::move() function, thus avoiding unnecessary memory copying and resource occupation.

Does move semantics be triggered when rvalue reference assignment is used?

Using rvalue reference assignment does not necessarily trigger move semantics, you need to see the code implementation in detail.

In C++11, if a class defines a move constructor and a move assignment operator, and these functions use rvalue references ( & amp; & amp; ) to pass parameters, then when we use the object’s rvalue Move semantics are triggered when a reference is assigned. For example:

Copy Codeclass MyClass {
public:
    // move constructor
    MyClass(MyClass & amp; & amp; other) {
        // transfer resource ownership
        data_ = other. data_;
        other.data_ = nullptr;
    }
    
    // move assignment operator
    MyClass & amp; operator=(MyClass & amp; & amp; other) {
        if (this != &other) {
            // Release the current resource
            delete data_;
            
            // transfer resource ownership
            data_ = other. data_;
            other.data_ = nullptr;
        }
        
        return *this;
    }
    
private:
    int* data_;
};

// assign using move semantics
MyClass a;
MyClass b(std::move(a)); // trigger move constructor

MyClass c;
c = std::move(b); // triggers the move assignment operator

In the above code, we use the std::move() function to convert the object a into an rvalue reference, thus triggering its move constructor; at the same time, we use the rvalue reference of object b to assign a value to object c, thus triggering its move assignment operator.

However, if a class does not define a move constructor and a move assignment operator, then when we use its rvalue reference for an assignment operation, its copy constructor and copy assignment operator will be triggered instead of move semantics. Therefore, when implementing a class, it is necessary to define or not define functions related to movement semantics according to the specific situation.

How are move semantics triggered inside a move constructor?

In the move constructor, we usually use an rvalue reference ( & amp; & amp; ) to accept an object and “move” its internal members to the current object. This process is achieved through move semantics.

Specifically, when we accept an rvalue reference parameter in the move constructor, the life cycle of the parameter is about to end, so the resources it occupies are about to be released. In order to avoid additional memory copy and resource occupation, we can directly point the pointer member of the current object to the memory space occupied by the original object, and set the pointer member of the original object to nullptr or other appropriate invalid state. In this way, although the current object and the original object both point to the same memory space, because the original object is no longer valid, there will be no accidental memory release or repeated resource occupation.

For example, in the following code, we define a simple Moveable class, which contains a pointer member data_:

Copy Codeclass Moveable {
public:
    // move constructor
    Moveable(Moveable & amp; & amp; other) {
        data_ = other. data_;
        other.data_ = nullptr; // set to null pointer
    }
    
private:
    int* data_;
};

In this example, when we use an rvalue reference to call the move constructor of the Moveable object, it will accept as a parameter a temporary object that will end its lifetime. Then, it transfers the object’s pointer member data_ to the current object, and sets the original object’s pointer member to nullptr. In this way, we have successfully implemented move semantics.

It should be noted that the move constructor can only be used for rvalue reference parameters, not for lvalue reference or const reference parameters. Therefore, when using move semantics, we need to ensure that the passed object is an rvalue type. If an lvalue object is passed in, the compiler will call the copy constructor instead of the move constructor.

[External link image transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the image and upload it directly (img-RO1kyn9B-1684891997880) (C:/Users/87242/AppData/Roaming/Typora/typora-user-images/ image-20230511101220967.png)]

In this example, when we use an rvalue reference to call the move constructor of the Moveable object, it will accept as a parameter a temporary object that will end its lifetime. Then, it transfers the object's pointer member data_ to the current object, and sets the original object's pointer member to nullptr. In this way, we have successfully implemented move semantics.

It should be noted that the move constructor can only be used for rvalue reference parameters, not for lvalue reference or const reference parameters. Therefore, when using move semantics, we need to ensure that the passed object is an rvalue type. If an lvalue object is passed in, the compiler will call the copy constructor instead of the move constructor.

[External link image transfer...(img-RO1kyn9B-1684891997880)]

[External link image transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the image and upload it directly (img-AmwE00wz-1684891997881) (C:/Users/87242/AppData/Roaming/Typora/typora-user-images/ image-20230511101354507.png)]