C++对象在作为函数返回值的拷贝研究

介绍

接上篇C++对象在作为函数参数的拷贝研究,研究对象在作为返回值时候的拷贝研究

测试对象代码

简单的测试对象,在每个不同的构造函数以及析构函数中打印log

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
class A
{
public:
A()
{
cout << "construct" << endl;
}

A(const A& a)
{
member = a.member;
cout << "copy construct" << endl;
}

A(A&& a)
{
member = std::move(a.member);
cout << "rvalue copy construct" << endl;
}

~A()
{
cout << "destruct" << endl;
}

A& operator=(const A& a)
{
member = a.member;
cout << "copy operator= function" << endl;
return *this;
}

A& operator=(A&& a)
{
member = a.member;
cout << "rvalue operator= function" << endl;
return *this;
}

public:
string member;
};

测试返回值

待测试函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
A exist_a;

A returnTempValue()
{
A a;
return a;
}

A returnExistValue()
{
return exist_a;
}

const A& returnExistValueByConstRef()
{
return exist_a;
}

测试用例

主要考虑有三种,存储返回值的情况,一种是临时对象去存储返回值,一种是以存在的对象去存储返回值,还有一种是引用(这里只考虑const引用,因为const能存储返回的临时对象)去存储返回值

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
int main()
{
A a;

{
{
// Test 1;
// 测试临时对象直接传值给临时对象;
cout << "====== Test 1: test return temp value hold by temp value" << endl;
A a = returnTempValue();
}

cout << endl;

{
// Test 2;
// 测试临时对象直接传值给已存在的对象;
cout << "====== Test 2: test return temp value hold by exist value" << endl;
a = returnTempValue();
}

cout << endl;

{
// Test 3;
// 测试临时对象直接传值给const引用;
cout << "====== Test 3: test return temp value hold by const ref" << endl;
const A &a = returnTempValue();
}

cout << endl;
}

{
{
// Test 4;
// 测试已存在的对象直接传值给临时对象;
cout << "====== Test 4: test return exist value hold by temp value" << endl;
A a = returnExistValue();
}

cout << endl;

{
// Test 5;
// 测试已存在的对象直接传值给已存在对象;
cout << "====== Test 5: test return exist value hold by exist value" << endl;
a = returnExistValue();
}

cout << endl;

{
// Test 6;
// 测试已存在对象直接传值给const引用;
cout << "====== Test 6: test return exist value hold by const ref" << endl;
const A &a = returnExistValue();
}

cout << endl;
}

{
{
// Test 7;
// 测试已存在的对象传const引用给临时对象;
cout << "====== Test 7: test return exist value by const ref hold by temp value" << endl;
A a = returnExistValueByConstRef();
}

cout << endl;

{
// Test 8;
// 测试已存在的对象传const引用给已存在对象;
cout << "====== Test 8: test return exist value by const ref hold by exist value" << endl;
a = returnExistValueByConstRef();
}

cout << endl;

{
// Test 9;
// 测试已存在对象传const引用给const引用;
cout << "====== Test 9: test return exist value by const ref hold by const ref" << endl;
const A &a = returnExistValueByConstRef();
}
}

while (1);
}

测试书结果

这里只给出Visual Studio 2017 release模式,g++ 4.8 -O0 -g模式以及g++ 4.8 -O3模式的输出,Visual Studio 2017 Debug模式一般不会直接用于生产环境,所以忽略
经过测试,Visual Studio 2017 Release 模式,g++ 4.8 -g -O0模式,g++ 4.8 -O3模式都输出如下内容

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
construct
construct
====== Test 1: test return temp value hold by temp value
construct
destruct

====== Test 2: test return temp value hold by exist value
construct
rvalue operator= function
destruct

====== Test 3: test return temp value hold by const ref
construct
destruct

====== Test 4: test return exist value hold by temp value
copy construct
destruct

====== Test 5: test return exist value hold by exist value
copy construct
rvalue operator= function
destruct

====== Test 6: test return exist value hold by const ref
copy construct
destruct

====== Test 7: test return exist value by const ref hold by temp value
copy construct
destruct

====== Test 8: test return exist value by const ref hold by exist value
copy operator= function

====== Test 9: test return exist value by const ref hold by const ref

测试小结

  1. 对比Test 1、Test 2、Test 3,可以发现如果使用临时对象去存储返回值,不管是否是否const引用,开销都一样,而且会比使用已存在的对象去存储返回值效率更高。为什么临时变量没有copy开销?是因为C++有一个返回值优化(Return Value Optimization,简称RVO)的功能,该优化的实现原理就是将返回的临时对象使用引用传参的方式传递给函数,这样就不会有不必要的copy开销
  2. 对比Test 4、Test 5、Test 6,可以得到总结1的相同结论
  3. 对比Test 7、Test 8、Test 9,可以发现,不论是传给临时对象还是已存在的对象消耗相同,需要一次copy构造,但比起使用const引用的0消耗还是差很多

总结

** 1. 如果返回的是已存在的对象,使用引用来做返回值,至于是否需要const应用,看是否需要外部对这个对象进行修改。**
** 2. 如果返回时临时对象,则函数返回值只能是传值。**
** 3. 外部在接收函数返回的对象的时候,如果不需要使用已存在的对象去存储,而且不会去更改返回的对象的情况,建议使用const引用去存储返回值,如果底层优化过了,使用了引用来返回对象的话,就可以不用更改就得到最高效率。**
** 4. 在写类的时候,增加右值构造以及右值赋值的方法,使用std::move来减少copy操作。**

完整测试代码

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#include <iostream>
#include <functional>
using namespace std;

class A
{
public:
A()
{
cout << "construct" << endl;
}

A(const A& a)
{
member = a.member;
cout << "copy construct" << endl;
}

A(A&& a)
{
member = std::move(a.member);
cout << "rvalue copy construct" << endl;
}

~A()
{
cout << "destruct" << endl;
}

A& operator=(const A& a)
{
member = a.member;
cout << "copy operator= function" << endl;
return *this;
}

A& operator=(A&& a)
{
member = a.member;
cout << "rvalue operator= function" << endl;
return *this;
}

public:
string member;
};

A exist_a;

A returnTempValue()
{
A a;
return a;
}

A returnExistValue()
{
return exist_a;
}

const A& returnExistValueByConstRef()
{
return exist_a;
}

int main()
{
A a;

{
{
// Test 1;
// 测试临时对象直接传值给临时对象;
// 编译器优化,不会有多余的copy操作;
cout << "====== Test 1: test return temp value hold by temp value" << endl;
A a = returnTempValue();
}

cout << endl;

{
// Test 2;
// 测试临时对象直接传值给已存在的对象;
// 如果对象定义了rvalue操作的话,消耗了一次move operator=操作,否则消耗一次copy操作;
cout << "====== Test 2: test return temp value hold by exist value" << endl;
a = returnTempValue();
}

cout << endl;

{
// Test 3;
// 测试临时对象直接传值给const引用;
// 同Test 1;
cout << "====== Test 3: test return temp value hold by const ref" << endl;
const A &a = returnTempValue();
}

cout << endl;
}

{
{
// Test 4;
// 测试已存在的对象直接传值给临时对象;
// 被赋值的临时对象会被copy构造;
cout << "====== Test 4: test return exist value hold by temp value" << endl;
A a = returnExistValue();
}

cout << endl;

{
// Test 5;
// 测试已存在的对象直接传值给已存在对象;
// 一次copy构造加一次move operator=操作;
cout << "====== Test 5: test return exist value hold by exist value" << endl;
a = returnExistValue();
}

cout << endl;

{
// Test 6;
// 测试已存在对象直接传值给const引用;
// 同Test 1;
cout << "====== Test 6: test return exist value hold by const ref" << endl;
const A &a = returnExistValue();
}

cout << endl;
}

{
{
// Test 7;
// 测试已存在的对象传const引用给临时对象;
// 消耗一次copy操作;
cout << "====== Test 7: test return exist value by const ref hold by temp value" << endl;
A a = returnExistValueByConstRef();
}

cout << endl;

{
// Test 8;
// 测试已存在的对象传const引用给已存在对象;
// 同Test 4;
cout << "====== Test 8: test return exist value by const ref hold by exist value" << endl;
a = returnExistValueByConstRef();
}

cout << endl;

{
// Test 9;
// 测试已存在对象传const引用给const引用;
// 无消耗;
cout << "====== Test 9: test return exist value by const ref hold by const ref" << endl;
const A &a = returnExistValueByConstRef();
}
}

while (1);
}