C++5大构造函数
Posted devbins
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++5大构造函数相关的知识,希望对你有一定的参考价值。
前言
自C++11以来,引入了移动构造函数和移动赋值函数,使得在构造对象的时候可以减少调用次数,以提高性能。
所以C++的构造函数从3个变成了5个,分别是构造函数、拷贝构造函数、拷贝赋值函数、移动构造函数、移动赋值函数。
它们非常相似,放在一起容易搞混,于是总结一下,便有了此文,希望能够对大家有所帮助。
先来看下面的这个例子
#include<iostream>
using namespace std;
class Test{
public:
Test() {
cout << "construct" << endl;
}
Test(const Test& test) {
cout << "Copy Construct" << endl;
}
Test& operator=(const Test& test) {
cout << "Copy Assignment Operator" << endl;
return *this;
}
Test(const Test&& test) {
cout << "Move construct" << endl;
}
Test& operator=(Test&& test) {
cout << "Move Assignment Operator" << endl;
return *this;
}
};
Test GenerateTest() {
return Test();
}
void PassByValue(Test test) {
}
Test&& MoveTest(Test& test) {
return move(test);
}
int main(int argc, char *argv[])
{
cout << "----------------1------------------" << endl;
Test t1;
cout << "----------------2------------------" << endl;
Test t2(t1);
cout << "----------------3------------------" << endl;
Test t3 = t1;
cout << "----------------4------------------" << endl;
Test t4 = Test();
cout << "----------------5------------------" << endl;
Test t5 = Test(t1);
cout << "----------------6------------------" << endl;
t5 = t2;
cout << "----------------7------------------" << endl;
Test t7 = move(t1);
cout << "----------------8------------------" << endl;
t7 = move(t2);
cout << "----------------9------------------" << endl;
Test t8 = GenerateTest();
cout << "----------------10------------------" << endl;
Test t9 = MoveTest(t1);
cout << "----------------11------------------" << endl;
Test&& t10 = MoveTest(t1);
cout << "----------------12------------------" << endl;
t9 = Test();
cout << "----------------13------------------" << endl;
PassByValue(t9);
return 0;
}
可以猜猜看,会输出什么?
输出
以下结果是开启了编译优化后的输出
----------------1------------------
construct
----------------2------------------
Copy Construct
----------------3------------------
Copy Construct
----------------4------------------
construct
----------------5------------------
Copy Construct
----------------6------------------
Copy Assignment Operator
----------------7------------------
Move construct
----------------8------------------
Move Assignment Operator
----------------9------------------
construct
----------------10------------------
Move construct
----------------11------------------
----------------12------------------
construct
Move Assignment Operator
----------------13------------------
Copy Construct
以下结果是关闭了编译器优化后的输出
在编译的时候加上 -fno-elide-constructors
来关闭优化
----------------1------------------
construct
----------------2------------------
Copy Construct
----------------3------------------
Copy Construct
----------------4------------------
construct
Move construct
----------------5------------------
Copy Construct
Move construct
----------------6------------------
Copy Assignment Operator
----------------7------------------
Move construct
----------------8------------------
Move Assignment Operator
----------------9------------------
construct
Move construct
Move construct
----------------10------------------
Move construct
----------------11------------------
----------------12------------------
construct
Move Assignment Operator
----------------13------------------
Copy Construct
分析
我们把开启优化和关闭优化放一起对比看
序号 | 开启优化 | 关闭优化 | 分析 |
---|---|---|---|
1 | construct | construct | 调用构造方法,没什么好说的 |
2 | Copy Construct | Copy Construct | 使用t1初始化t2,创建新对象,调用拷贝构造函数 |
3 | Copy Construct | Copy Construct | 这里就有点迷惑了,t3是个新对象,所以也是调用拷贝构造函数,而不是拷贝赋值函数 |
4 | construct | construct | 开启优化后只需要一次构造 |
Move construct | 关闭优化还需要一次移动构造函数,因为 Test() 是个右值 | ||
5 | Copy Construct | Copy Construct | 开启优化后只需要一次拷贝构造 |
Move construct | 关闭优化和上一个一样,由于是右值还需要调用一次移动构造 | ||
6 | Copy Assignment Operator | Copy Assignment Operator | 由于t5已经存在了,所以调用拷贝赋值函数 |
7 | Move construct | Move construct | 把t1转换成右值,调用移动构造函数 |
8 | Move Assignment Operator | Move Assignment Operator | 由于t7已经存在,所以调用移动赋值函数 |
9 | construct | construct | 开启优化只需要一次构造 |
Move construct | 关闭优化,需要调用两次移动构造,一次是return返回给一个临时对象 | ||
Move construct | 另一次是把临时对象赋值给t8,临时对象是右值,所以都调用移动构造函数 | ||
10 | Move construct | Move construct | 使用 move 把test转为右值,调用移动构造函数 |
11 | 只是进行右值绑定,没有创建对象或更新对象,不会调用构造函数 | ||
12 | construct | construct | t9是个已经存在的对象,而Test()是个右值,所以先调用构造函数 |
Move Assignment Operator | Move Assignment Operator | 然后调用移动构造函数 | |
13 | Copy Construct | Copy Construct | 以传值的方式给形参,调用拷贝构造函数 |
总结
这些构造函数老是把人搞得晕头转向,过几天不看就会忘记搞混,我的记忆方法是抓住两个关键,一个是等号左边的对象,另一个是等号右边的对象。
等号左边的对象决定的是构造还是赋值,也就是如果左边的对象是不存在的就要构造一个新的对象,调用拷贝/移动 构造 。如果对象是存在就是要更新对象的值,也就是要调用拷贝/移动 赋值
等号右边的对象决定的是移动还是拷贝,也就是如果右边的对象是一个右值那么就会调用 移动 构造/赋值。如果右边的对象不是一个右值,那么表示这个对象可能还需要使用,不能移走,所以要调用 拷贝 构造/赋值。
还有一种情况是没有 =
的,这个就很好判断了。无非也就构造和拷贝构造,如果只有一个对象,那就不存在拷贝不拷贝的,肯定是构造,对应上文中的 1
。如果有两个对象,那肯定是拷贝构造,不存在赋值的情况,对应上文中的 2
。
用一个表格来展示它们之间的关系
对象不存在 | 对象存在 | |
---|---|---|
左值 | 拷贝构造 | 拷贝赋值 |
右值 | 移动构造 | 移动赋值 |
拷贝构造函数和移动构造函数 - 简书: https://www.jianshu.com/p/f5d48a7f5a52
以上是关于C++5大构造函数的主要内容,如果未能解决你的问题,请参考以下文章
Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段
Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段