C++的右值和移动构造函数的测试程序说明
请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com
本文用以测试C++的右值,移动构造函数,以及标准库容器的emplace和push操作的区别。 测试环境是Visual C++ 14.0.25431.01 Update 3版本,未启用任何编译优化操作
代码可以在github中获取。
1#include <cstring>
2#include <cstdlib>
3#include <map>
4#include <vector>
5#include <cstdio>
6
7class DemoClassA
8{
9public:
10 DemoClassA() :num_(0), name_(nullptr)
11 {
12 std::printf("DemoClassA::DemoClassA()\n");
13 }
14
15 ~DemoClassA()
16 {
17 if (name_)
18 {
19 std::printf("DemoClassA::~DemoClassA(%d,%s)\n", num_, name_);
20 delete[] name_;
21 name_ = nullptr;
22 }
23 else
24 {
25 std::printf("DemoClassA::~DemoClassA()\n");
26 }
27 }
28
29 DemoClassA(int num, const char* name)
30 {
31 num_ = num;
32 auto len = std::strlen(name) + 1;
33 name_ = new char[len];
34 memset(name_, 0, len);
35 std::strcpy(name_, name);
36 std::printf("DemoClassA::DemoClassA(%d,%s)\n", num_, name_);
37 }
38
39 DemoClassA(const DemoClassA& rhs)
40 {
41 num_ = rhs.num_;
42 auto len = std::strlen(rhs.name_) + 1;
43 name_ = new char[len];
44 memset(name_, 0, len);
45 std::strcpy(name_, rhs.name_);
46 std::printf("DemoClassA::DemoClassA(const DemoClassA& rhs)\n");
47 }
48
49 DemoClassA(DemoClassA&& rhs)
50 {
51 num_ = rhs.num_;
52 name_ = rhs.name_;
53 rhs.num_ = 0;
54 rhs.name_ = nullptr;
55 std::printf("DemoClassA::DemoClassA(DemoClassA&& rhs)\n");
56 }
57
58 DemoClassA& operator = (const DemoClassA& rhs)
59 {
60 if (name_)
61 {
62 delete[] name_;
63 name_ = nullptr;
64 }
65
66 num_ = rhs.num_;
67 auto len = std::strlen(rhs.name_) + 1;
68 name_ = new char[len];
69 memset(name_, 0, len);
70 std::strcpy(name_, rhs.name_);
71 std::printf("DemoClassA::operator = (const DemoClassA& rhs)\n");
72 return *this;
73 }
74
75 DemoClassA& operator = (DemoClassA&& rhs)
76 {
77 if (name_)
78 {
79 delete[] name_;
80 name_ = nullptr;
81 }
82
83 num_ = rhs.num_;
84 name_ = rhs.name_;
85 rhs.num_ = 0;
86 rhs.name_ = nullptr;
87 std::printf("DemoClassA::operator = (DemoClassA&& rhs)\n");
88 return *this;
89 }
90
91 void PrintInfo()
92 {
93 if (name_)
94 std::printf("DemoClassA: I am %s.My number is %d\n", name_, num_);
95 else
96 std::printf("DemoClassA: I am a dummy\n");
97 }
98
99 void ChangeNumber(int new_num)
100 {
101 num_ = new_num;
102 }
103
104private:
105 int num_ = 0;
106 char* name_ = nullptr;
107};
108
109class DemoClassB
110{
111public:
112 DemoClassB() :num_(0), name_(nullptr)
113 {
114 std::printf("DemoClassB::DemoClassB()\n");
115 }
116
117 ~DemoClassB()
118 {
119 if (name_)
120 {
121 std::printf("DemoClassB::~DemoClassB(%d,%s)\n", num_, name_);
122 delete[] name_;
123 name_ = nullptr;
124 }
125 else
126 {
127 std::printf("DemoClassB::~DemoClassB()\n");
128 }
129 }
130
131 DemoClassB(int num, const char* name)
132 {
133 num_ = num;
134 auto len = std::strlen(name) + 1;
135 name_ = new char[len];
136 memset(name_, 0, len);
137 std::strcpy(name_, name);
138 std::printf("DemoClassB::DemoClassB(%d,%s)\n", num_, name_);
139 }
140
141 DemoClassB(const DemoClassB& rhs)
142 {
143 num_ = rhs.num_;
144 auto len = std::strlen(rhs.name_) + 1;
145 name_ = new char[len];
146 memset(name_, 0, len);
147 std::strcpy(name_, rhs.name_);
148 std::printf(
149 "DemoClassB::DemoClassB(const DemoClassB& rhs %d,%s)\n", num_, name_);
150 }
151
152 DemoClassB& operator = (const DemoClassB& rhs)
153 {
154 if (name_)
155 {
156 delete[] name_;
157 name_ = nullptr;
158 }
159
160 num_ = rhs.num_;
161 auto len = std::strlen(rhs.name_) + 1;
162 name_ = new char[len];
163 memset(name_, 0, len);
164 std::strcpy(name_, rhs.name_);
165 std::printf("DemoClassB::operator = (const DemoClassB& rhs)\n");
166 return *this;
167 }
168
169 void PrintInfo()
170 {
171 if (name_)
172 std::printf(
173 "DemoClassB: I am %s.My number is %d\n", name_, num_);
174 else
175 std::printf("DemoClassB: I am a dummy\n");
176 }
177
178 void ChangeNumber(int new_num)
179 {
180 num_ = new_num;
181 }
182
183private:
184 int num_;
185 char* name_;
186};
187
188
189DemoClassA MakeDemoClassAInstance1(int num, const char* name)
190{
191 return DemoClassA(num, name);
192}
193
194DemoClassA MakeDemoClassAInstance2(int num, const char* name)
195{
196 DemoClassA a(num, name);
197 return a;
198}
199
200DemoClassB MakeDemoClassBInstance1(int num, const char* name)
201{
202 // 因为这里没有别的操作,只是一个简单的return,所以就算不开优化
203 // 这个MakeDemoClassBInstance1函数实质上是等同于不存在的,
204 return DemoClassB(num, name);
205}
206
207DemoClassB MakeDemoClassBInstance2(int num, const char* name)
208{
209 // 因为DemoClassB没有移动构造函数,所以返回b对象所对应的临时
210 // 对象时,会调用DemoClassB的拷贝构造函数生成这个临时对象
211 DemoClassB b(num, name);
212 return b;
213}
214
215void main()
216{
217 std::vector<DemoClassA> VecDemoClassA;
218 std::vector<DemoClassB> VecDemoClassB;
219
220 VecDemoClassA.reserve(10);
221 VecDemoClassB.reserve(10);
222
223 // 从执行结果可以看出,调用MakeDemoClassAInstance1函数后,只发生了调用DemoClassA
224 // 构造函数这一次函数调用。所以哪怕没有开任何的优化开关,这个MakeDemoClassAInstance1
225 // 函数直接优化掉了
226 std::printf("MakeDemoClassAInstance1 begins ---->>>>\n");
227 auto dca1 = MakeDemoClassAInstance1(0, "John");
228 std::printf("MakeDemoClassAInstance1 ends ------<<<<\n\n");
229
230 // 从执行结果可以看出,调用MakeDemoClassAInstance2函数后,调用DemoClassA
231 // 构造函数1次,移动构造函数1次,析构函数一次,也就是说,如果类明确定了它的移动构造函数
232 // 则发生临时对象的生成的传递时,优先调用移动构造函数
233 std::printf("MakeDemoClassAInstance2 begins ---->>>>\n");
234 auto dca2 = MakeDemoClassAInstance2(1, "Bob");
235 std::printf("MakeDemoClassAInstance2 ends ------<<<<\n\n");
236
237 // 从执行结果可以看出,调用MakeDemoClassBInstance1函数后,只发生了调用DemoClassB
238 // 构造函数这一次函数调用。所以哪怕没有开任何的优化开关,这个MakeDemoClassBInstance1
239 // 函数直接优化掉了
240 std::printf("MakeDemoClassBInstance1 begins ---->>>>\n");
241 auto dcb1 = MakeDemoClassBInstance1(0, "Lucy");
242 std::printf("MakeDemoClassBInstance1 ends ------<<<<\n\n");
243
244 // 从执行结果可以看出,调用MakeDemoClassBInstance2函数后,调用DemoClassB
245 // 构造函数1次,拷贝构造函数1次,析构函数一次,也就是说,如果类没有明确定了它的移动构造
246 // 函数,则发生临时对象的生成的传递时,使用拷贝构造函数
247 std::printf("MakeDemoClassBInstance2 begins ---->>>>\n");
248 auto dcb2 = MakeDemoClassBInstance2(1, "Lily");
249 std::printf("MakeDemoClassBInstance2 ends ------<<<<\n\n");
250
251 // 从执行结果可以看出,std::vector的push_back方法会拷贝一个副本到vector里面,因为
252 // 且“DemoClassA(4, "Ken")”这样的语句生成了一个右值对象,且DemoClassA有移动构造函
253 // 数,所以是使用了移动构造函数,构造了一个对象副本放进vector里
254 std::printf("std::vector<DemoClassA>::push_back begins ---->>>>\n");
255 VecDemoClassA.push_back(DemoClassA(4, "Ken"));
256 std::printf("std::vector<DemoClassA>::push_back ends ------<<<<\n\n");
257
258 // 从执行结果可以看出,如果先构造一个DemoClassA的实例对象dcb11,然后再调用std::vector的
259 // push_back方法时,如果不使用std::move方法将dcb11转为右值的话,调用拷贝构造函数构造一个
260 // 副本,如使用std::move方法将dcb11转为右值,则调用移动构造函数构造副本放进vector
261 // 一般地,被std::move处理过的对象,它持有的变量被偷了,所以一般不要随便再继续使用它
262 std::printf("std::vector<DemoClassA>::push_back with move begins ---->>>>\n");
263 auto dcb11 = DemoClassA(74, "Obama");
264 VecDemoClassA.push_back(dcb11);
265 VecDemoClassA.push_back(std::move(dcb11));
266 dcb11.PrintInfo(); // dcb11的name_指向的资源已经被偷走了,name_已经被置为nullptr
267 std::printf("std::vector<DemoClassA>::push_back with move ends ------<<<<\n\n");
268
269 // 从执行结果可以看出,std::vector的push_back方法会拷贝一个副本到vector里面,因为
270 // DemoClassA只有拷贝构造函数,所以是使用了拷贝构造函数构造了一个对象副本放进vector里
271 std::printf("std::vector<DemoClassB>::push_back begins ---->>>>\n");
272 VecDemoClassB.push_back(DemoClassB(5, "David"));
273 std::printf("std::vector<DemoClassB>::push_back ends ------<<<<\n\n");
274
275 // 从执行结果可以看出,emplace_back会直接生成给一个临时对象push到vector里
276 std::printf("std::vector<DemoClassA>::emplace_back begins ---->>>>\n");
277 VecDemoClassA.emplace_back(14, "Kate");
278 std::printf("std::vector<DemoClassA>::emplace_back ends ------<<<<\n\n");
279
280 // 从执行结果可以看出,emplace_back会直接生成给一个临时对象push到vector里
281 std::printf("std::vector<DemoClassB>::emplace_back begins ---->>>>\n");
282 VecDemoClassB.emplace_back(24, "Fancy");
283 std::printf("std::vector<DemoClassB>::emplace_back ends ------<<<<\n\n");
284
285 system("PAUSE");
286}
程序的执行输出如下图: