C++ return一个对象的执行细节

同学发过来一道考试题,要求解释输出。好久没有用过这些知识了,所以顺便复习下。

代码

为了搞清楚里面知识,所以把代码增加了地址的输出来进行实验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <limits.h>
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <map>
#include <string>
#include <utility>
#include <vector>

using namespace std;

class Solution {
public:
Solution(): id_(0) {
cout << "Default Constructor:id="
<< id_ << " addr=:" << this << endl;
}
Solution(int id): id_(id) {
cout << "Constructor with Paramter:id="
<< id_ << " addr=:" << this << endl;
}
Solution(const Solution&course): id_(course.id_) {
cout << "Copy Constructor :id="
<< id_ << " addr=:" << this << endl;
}
~Solution() {
cout << "Destructor:id="
<< id_ << " addr=:" << this << endl;
}
int GetId() {return id_;};

private:
int id_;
};

Solution Max(Solution a, Solution b) {
if (a.GetId() > b.GetId()) {
cout << "end " << endl;
return a;
cout << "end after return" << endl;
} else {
cout << "end " << endl;
return b;
cout << "end after return" << endl;
}
}

void Q2() {
Solution class1(1);
//Solution class2;
Solution class2 = class1;
//class2 = class1;
Solution classX(5), classY(6);
Solution classZ;
// classZ = class1;
// Max(classX, classY);
classZ = Max(classX, classY);
cout << "after max" << endl;
Solution *pClasses = new Solution[3];
delete [] pClasses;
}
int main(int argc, char const *argv[]) {
/* code */
Q2();
system("pause");
return 0;
}

输出的结果是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Constructor with Paramter:id=1  addr=:0x61fedc 
Copy Constructor :id=1 addr=:0x61fed8
Constructor with Paramter:id=5 addr=:0x61fed4
Constructor with Paramter:id=6 addr=:0x61fed0
Default Constructor:id=0 addr=:0x61fecc
Copy Constructor :id=6 addr=:0x61fee4
Copy Constructor :id=5 addr=:0x61fee8
end
Copy Constructor :id=6 addr=:0x61fee0
Destructor:id=6 addr=:0x61fee0
Destructor:id=5 addr=:0x61fee8
Destructor:id=6 addr=:0x61fee4
after max
Default Constructor:id=0 addr=:0xfc1d74
Default Constructor:id=0 addr=:0xfc1d78
Default Constructor:id=0 addr=:0xfc1d7c
Destructor:id=0 addr=:0xfc1d7c
Destructor:id=0 addr=:0xfc1d78
Destructor:id=0 addr=:0xfc1d74
Destructor:id=6 addr=:0x61fecc
Destructor:id=6 addr=:0x61fed0
Destructor:id=5 addr=:0x61fed4
Destructor:id=1 addr=:0x61fed8
Destructor:id=1 addr=:0x61fedc

可以看到输出结果与题目中的结果是有差别的,题目中的篮圈析构顺序是566,实验结果是656

解释

在调用Max之前的输出结果很容易理解

1
2
3
4
5
Constructor with Paramter:id=1  addr=:0x61fedc  # class1 的参数构造
Copy Constructor :id=1 addr=:0x61fed8 #Solution class2 = class1的拷贝构造
Constructor with Paramter:id=5 addr=:0x61fed4 #classX的参数构造
Constructor with Paramter:id=6 addr=:0x61fed0 #classY的参数构造
Default Constructor:id=0 addr=:0x61fecc #classZ的默认构造

进入Max函数后从右向左依次进行参数的压栈,所以首先是b的拷贝构造,然后是a的拷贝构造。

接下来是一个很重要的细节,在执行return的时候会自动执行一个拷贝构造来生成一个临时的对象temp用来返回。

1
2
3
4
Copy Constructor :id=6  addr=:0x61fee4 			#b的拷贝构造
Copy Constructor :id=5 addr=:0x61fee8 #a的拷贝构造
end
Copy Constructor :id=6 addr=:0x61fee0 #return 产生的临时对象temp的拷贝构造

在Max函数中参数入栈的顺序是:先是b然后是a最后是temp。

需要注意的是,在执行完return之后并没有立即就执行了出栈的操作。需要在完成classZ = Max(classX, classY); 这个赋值语句后才会执行出栈操作对栈中的对象执行析构操作。析构的顺序就是出栈的顺序,先入后出,后入先出。所以首先是temp变量的析构,然后是b这个形参的析构,最后才是a这个形参的析构。

所以参考链接里面的顺序也是有误的,并不是首先析构形参。

1
2
3
4
after max
Default Constructor:id=0 addr=:0xfc1d74 # temp变量的析构
Default Constructor:id=0 addr=:0xfc1d78 # b这个形参的析构
Default Constructor:id=0 addr=:0xfc1d7c # a这个形参的析构

之后因为使用new来创建的数据,其内存是分配在堆上面的并没有在Max的函数栈中,需要手动delete来销毁来防止内存泄漏。因此首先是3个0个构造和析构。

1
2
3
4
5
6
7
8
# new
Default Constructor:id=0 addr=:0xfc1d74
Default Constructor:id=0 addr=:0xfc1d78
Default Constructor:id=0 addr=:0xfc1d7c
#delete
Destructor:id=0 addr=:0xfc1d7c
Destructor:id=0 addr=:0xfc1d78
Destructor:id=0 addr=:0xfc1d74

最后是对Max这个函数中入栈的局部变量执行出栈与销毁的操作。

1
2
3
4
5
Destructor:id=6 addr=:0x61fecc 		#classZ的析构
Destructor:id=6 addr=:0x61fed0 #classY的析构
Destructor:id=5 addr=:0x61fed4 #classX的析构
Destructor:id=1 addr=:0x61fed8 #class2的析构
Destructor:id=1 addr=:0x61fedc #class1的析构

总结

主要是两个点比较迷惑

  1. Max函数return 的操作是通过自动调用拷贝构造一个临时对象来执行的返回的,临时对象的析构需要等到classZ = Max(classX, classY);语句执行完毕后
  2. 如果先构造了一个对象,然后再使用=来进行赋值是不会执行拷贝构造的,不是只要使用了=就会执行拷贝构造。所以Solution class2 = class1;这个语句没有先构造对象,因此执行了拷贝构造。而classZ = Max(classX, classY); 这个语句中,classZ事先用Solution classZ;构造过,所以不会再执行拷贝构造的操作。这个=只负责值之间的传递。

参考

https://blog.csdn.net/fruitz/article/details/41624017 析构顺序有误