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}

程序的执行输出如下图:

参考网页

c++11 之emplace_back 与 push_back的区别