Detailed analysis of when C++ constructor, copy construction, assignment function and move construction are called

1. Introduction to five functions

Constructor : responsible for the initialization of the object, the constructor can be overloaded, but it cannot be added before the constructorvirtual

Destructor : Responsible for cleaning up (freeing memory) before canceling the object. The destructor cannot be overloaded. There is only one destructor in a class

Copy constructor : A special constructor that uses an object of the same type to construct and initialize another object. The function name is consistent with the class name, and there is only one parameter, which is a modified constreference variable of this type

Assignment constructor : When an object of a class assigns a value to another object of the class, the assignment function of the class will be used, that is, the operator is overloaded =to complete the corresponding object assignment operation (here involves deep and shallow copying issues)

Move constructor : When an rvalue is used for initialization or assignment, the move constructor or move assignment operator is called to move resources, thereby avoiding copying and improving efficiency.


2. How to distinguish between lvalue and rvalue? What do you think?

Judgment method: 可以取地址的变量就是左值,不可以的就是右值

type meaning
Pan lvalue (glvalue) an expression whose value determines the identity of an object or function
Pure rvalue (prvalue) An expression that satisfies one of the following:
① calculates the value of an operand of an operator (such a prvalue has no result object)
② initializes an object (such a prvalue is said to have a result object)
xvalue (xvalue) glvalue representing an object or bitfield whose resources can be reused (via the move constructor)
lvalue glvalues ​​that are not xvalues
rvalue prvalue or xvalue

Note: The x value is the same concept as the x value.


3. Three usages of anonymous objects

Case 1 : 没有被对象接收, call the destructor immediately after executing the line to release the memory it occupies.

Case 2 : 用来初始化对象When using an anonymous object to initialize a new object, it can be understood as follows: the anonymous object is created, but it is used to initialize the new object, which is equivalent to the anonymous object becoming a well-known object. So only copy construction exists.

Case 3 : 用来赋值给对象, when an anonymous object is used to assign a value to an existing object, the assignment construction will occur at this time, and the assignment function will be called, and the memory occupied by the anonymous object will be released after the line is executed.

Example tests for three situations:

class Person{
    
    
public:
	// 析构函数
	~Person() {
    
     std::cout << "destroy...\n";}
	// 构造函数
	Person() {
    
     std::cout << "default constructor...\n"; }	
	// 赋值函数
	Person& operator=(const Person& p) {
    
    
		std::cout << "assign function...\n";
		return *this;
	}
};
Person f() {
    
     return Person(); }
int main()
{
    
    
	// 情况一:匿名对象没有被对象接收
	Person(); 
	
	// 情况二:用建匿名对象来初始化对象
	Person p_2 = Person(); // 等价于 Person p_2 = f();

	// 情况三:用匿名对象来赋值给已存在对象
	Person p_3;
	p_3 = f();
	return 0;
}

4. The code verifies each function call in detail

The code is detailed and simple, and each step has been commented, which is easy to understand.

class Person
{
    
    
public:
	 // 析构函数
	~Person() {
    
     std::cout << "destroy...\n";}
	
	// 默认构造函数
	Person() {
    
     std::cout << "default constructor...\n"; }
	
	// 拷贝构造函数
	Person(const Person& p) {
    
     std::cout << "copy constructor...\n"; }
	
	// 移动构造函数
	//Person(Person&& p) { std::cout << "move constructor...\n"; }
	
 	// 赋值函数
	Person& operator=(const Person& p) {
    
    
		std::cout << "assign function...\n";
		return *this;
	}
};

// 1.调用拷贝构造函数
void f_1(Person p) {
    
    }

// 2.不会调用拷贝构造函数
void f_2(Person& p) {
    
    }

// 3.调用默认构造函数
Person f_3() {
    
    
	Person p;
	return p;//注意p它是一个将亡值,如果此时存在移动构造是会调用移动构造的
}

// 4.调用默认构造函数
Person f_4() {
    
     return Person(); }

4.1 Test f_1 function (function parameter test – value transfer)

void f_1(Person p) {
    
    }

Test code:

Person p_1;
f_1(p_1);

Running result:
insert image description here
result analysis:
the first default construction: called when [object p_1] is declared;
the second copy construction: [object p_1] is called when passing a value to [formal parameter object p]; the
third destructor: function At the end, the memory occupied by the formal parameter object p is released.


4.2 Test f_2 function (function parameter test – reference transfer)

void f_2(Person& p) {
    
    }

Test code:

Person p_2;
f_2(p_2);

Running results:
insert image description here
Result analysis:
The first default construction: called when [object p_2] is declared;
Note: Because the function parameter is an object reference of this type, there is no copy construction, and no memory will be released after the function is executed .


4.3 Test f_3 function (function return value test - named object)

Person f_3() {
    
    
	Person p;
	return p;//注意p它是一个将亡值,如果此时存在移动构造是会调用移动构造的
}

4.3.1 Test code-1 (initialize new object)

Person p_3 = f_3();

Running result:
insert image description here
result analysis:
the first default construction: called when [object p] is declared in the function body; the
second copy construction: the [object p] created in the function body is returned, and then assigned to [object p_3] called (Note: Since the object in the function body is not [anonymous object], it cannot be directly converted into a named [object p_3], it needs to be called 拷贝构造to [对象p]的数据copy it to [对象p_3]) ;
the third destructor: after the copy construction is executed, it will be in the function Called when the [object p] created in the body is destructed.

Important: If the class exists at this time 移动构造函数(remove the comment), then the copy constructor will not be called, but themove constructor(to avoid copying a large amount of data), the results are as follows:
insert image description here


4.3.2 Test code-2 (assignment to an existing object)

Person p_3;  
p_3 = f_3();

Running result:
insert image description here
result analysis:
the first constructor: called when [object p_3] is declared;
the second constructor: called when [object p] is declared in the function body;
the third copy construction: the [object p_3] declared in the function body is called Called when the data of the object p] is copied to a [temporary object] (if it is in the class 移动构造函数的注释去掉, the copy constructor will not be called, but the move constructor will be called );
the fourth destructor: declared in the function body [object p] is called when it is destructed;
the fifth assignment function: it is called when the data of [temporary variable] is copied to [object p_3]; the
sixth destructor function: after the copy construction of [temporary variable] is completed, it is called The destructor releases the occupied memory.

Why not just call the copy constructor and be done?
 In [4.3.1 Test Code-1], we can see that when using the object returned by the function to initialize a new object, it only needs to call the copy construction or move construction once, and there is no case where the copy function is called, so why does 4.3.2 test Code -2 needs to be called?

Reason Analysis:
 Reason 1: Execution Person p_3results in the creation of [object p_3], and the function returns a created object (not an anonymous function). The use between two existing objects is an =assignment operation, and the copy construction will not be called. or mobile constructed.
 Reason 2: Due to reason 1, the [object p] returned by the function is useless, and the [object p] returned by the function will be released by the destructor.
 Because of the second reason, the [object p] returned by the function will be destroyed. Note that it has not been assigned to [object p_3] at this time, so [object p] needs to copy the data of [object p] to A [temporary object] (调用拷贝构造), [object p] is destructed after the copy is complete. Finally, just assign the data of the [temporary object] to the existing [object p_3] (调用赋值函数). After the assignment is completed, the [temporary object] will be destroyed.
 So far, all the above steps have been analyzed.


4.4 Test f_4 function (function return value test – anonymous object)

Person f_4() {
    
     return Person(); }

Test code:

Person p_4 = f_4();

operation result:
insert image description here

Result analysis:
声明 [对象p_4] 的同时直接使用 [匿名对象] 去初始化,此时 [匿名对象] 会直接转化成 [有名对象p_4] ( Anonymous object usage 2 ), so in this case only the copy constructor and move constructor will not be called.


If it is the following separation situation (declare first, then use anonymous object assignment):

Person p_4;
p_4 = f_4();

Running result:
insert image description here
result analysis:
the first default construction: called when [object p_4] is declared;
the second default construction: called when [anonymous object] in the function body is created;
the third assignment function: [anonymous object] Called when assigning to an existing [object p_4] ( anonymous object usage 3, when using an anonymous object to assign to an existing object, the assignment structure will occur at this time, and the assignment function will be called, and the anonymous object will also be executed after the line The memory occupied by the object is released );
the fourth destructor: After the assignment is completed, the memory occupied by the [anonymous object] is called when the memory is released.


5. Complete test code

#include<iostream>
#include<string>
using std::cout;
using std::cin;
using std::endl;
using std::string;

class Person
{
    
    
public:
	// 析构函数
	~Person() {
    
     std::cout << "destroy...\n"; }

	// 默认构造函数
	Person() {
    
     std::cout << "default constructor...\n"; }

	// 拷贝构造函数
	Person(const Person& p) {
    
     std::cout << "copy constructor...\n"; }

	// 移动构造函数
	//Person(Person&& p) { std::cout << "move constructor...\n"; }

	// 赋值函数
	Person& operator=(const Person& p) {
    
    
		std::cout << "assign function...\n";
		return *this;
	}
};

// 1.调用拷贝构造函数
void f_1(Person p) {
    
    }

// 2.不会调用拷贝构造函数
void f_2(Person& p) {
    
    }

// 3.调用默认构造函数
Person f_3() {
    
    
	Person p;
	return p;//注意p它是一个将亡值(右值的一种)
}

// 4.调用默认构造函数
Person f_4() {
    
     return Person(); }

int main()
{
    
    
	//Person();
	//Person p = Person();

	cout << "------测试f_1函数------\n";
	Person p_1;
	f_1(p_1);
	cout << "-----------------------\n\n";

	cout << "------测试f_2函数------\n";
	Person p_2;
	f_2(p_2);
	cout << "----------------------\n\n";

	cout << "------测试f_3函数------\n";
	Person p_3;
	p_3 = f_3();

	//Person p_3 = f_3();
	cout << "----------------------\n\n";

	cout << "------测试f_4函数------\n";
	Person p_4 = f_4();
	cout << "----------------------\n\n";

	return 0;
}

Guess you like

Origin blog.csdn.net/CSDN_Yuanyuan/article/details/129458407