本文测试内容大部分都是参考该博文 https://www.cnblogs.com/cposture/p/4925736.html
该博文作者深入汇编,写的十分详细,感谢作者大大。
之所以挪一次,是想精简一下,并且补充一些东西。
简述
先简单看看直接初始化和拷贝初始化的定义,然后再详细试验一下不同的拷贝初始化场景会调用哪种拷贝函数。实验环境 vs2017 已关闭编译器优化。
直接初始化和拷贝初始化
类的初始化分为直接初始化和拷贝初始化。
直接初始化就是在定义对象时不使用 = 号的初始化方式。调用普通构造函数。
string s1("123"); string s2(s1);
在c++11中,容器调用 emplace 成员函数创建的元素也进行直接初始化。
而拷贝初始化分很多情况,将会在以下情况发生。
- 在用 = 定义变量时。
- 函数调用时将 实参对象 传给 非引用的形参对象。
- 函数返回时返回非引用类型的对象。
- 用花括号列表初始化一个数组中的元素或聚合类中的成员。
- 某些类的某些函数也会拷贝初始化,例如容器的 insert 和 push 成员。
需要注意的是
- 拷贝初始化将跟据不同的情况调用不同的拷贝构造函数。
- 直接初始化有时会调用拷贝构造函数。例如
String s3(s1);
- 拷贝初始化过程中类类型的隐式转化不可忽略。许多时候,比较奇怪的语法往往是类类型隐式转化造成。
开始实验
#include <iostream> #include <cstring> using namespace std; class ClassTest { public: ClassTest( ) { c[0] = '\0'; cout << "ClassTest()" << endl; } ClassTest& operator=(const ClassTest &ct) //赋值运算符 { strcpy(c, ct.c); cout << "ClassTest& operator=(const ClassTest &ct)" << endl; return *this; } ClassTest(ClassTest&& ct) //移动构造函数 { cout << "ClassTest(ClassTest&& ct)" << endl; } ClassTest & operator=(ClassTest&& ct) //移动赋值运算符 { strcpy(c, ct.c); cout << "ClassTest & operator=(ClassTest&& ct)" << endl; return *this; } ClassTest(const char *pc) //普通构造函数 { strcpy(c, pc); cout << "ClassTest (const char *pc)" << endl; } ClassTest(const ClassTest& ct) //拷贝构造函数 { strcpy(c, ct.c); cout << "ClassTest(const ClassTest& ct)" << endl; } private: char c[256]; }; ClassTest f1( ) { ClassTest c; return c; } ClassTest f2( ) { return ClassTest( ); } void f3(ClassTest ct) { ; } int main( ) { ClassTest ct1("ab");//直接初始化 ClassTest ct2 = "ab";//复制初始化 ClassTest ct3 = ct1;//复制初始化 cout << "-------3" << endl; ClassTest ct4(ct1);//直接初始化 ClassTest ct5 = ClassTest("ab");//复制初始化 ClassTest ct6 = f1( ); cout << "-------6" << endl; f1( ); cout << "-------1" << endl; f2( ); cout << "-------2" << endl; f3(ct1); return 0; }
下面一句一句解析这个结果。
1. ClassTest ct1("ab");
这句和我们预期一样,直接调用相应的普通构造函数。
2. ClassTest ct2 = "ab";
这句本应该是由 “ab” 实参隐式构造一个临时对象,然后通过该临时对象调用移动构造函数构造ct2。但实际上被编译器优化成1的方式。即临时对象被编译器优化掉了
3. ClassTest ct3 = ct1;
4. ClassTest ct4 (ct1);
4 与 5 都与我们预期相符,调用拷贝构造函数。
5. ClassTest ct5 = ClassTest(“ab”);
6. ClassTest ct6 (ClassTest(“ab”));
5 和 6 本应都是由 “ab” 实参构造一个临时对象,然后通过该临时对象调用移动构造函数构造 ct5 和ct6。但实际上被编译器优化成1的方式。即临时对象被编译器优化掉了
与 2 不同的是,这块是显式生成临时对象,如果把构造函数 explicit 声明也没有影响,而2将编译不通过。
7. ClassTest ct7 = f1();
8. f1();
7 和 8 结果有点出乎意料,实际上这两个过程都是相同的,区别在于调用 f1() 时,7 是将ct7的地址传给函数,而 8 是将主函数栈上的临时对象的地址传入函数内。在函数内都先为局部对象调用默认构造函数,然后将这个局部对象作为实参分别调用相应的移动构造函数构造 ct7 和临时量。
9. f2();
按照7 和8 的分析,这个不难看出,是将一个主函数栈上临时对象的地址传进函数内,在函数结尾处默认构造这个临时对象。
10. f3(ct1);
真的就是普通的拷贝构造函数。
总结
通过上面实验过程,我们可以发现,在编译器能不用临时量就不用临时量,如2 ,5,6。
如果看不过瘾可以看看原作者的详细博文 。
来源:https://www.cnblogs.com/starrys/p/12174877.html