往期地址:
本期主题:
移动构造函数
1.左值和右值
1.1 定义
- 左值是能够出现在赋值符号左边的东西;例如a = b + 20,其中a就为左值,b+20就为右值;
- 右值是可以出现在赋值符号右边的东西;
1.2 左值、右值引用
可以简单理解,左值引用就是对左值的引用,右值引用就是对右值的引用,例如
int a = 10;
int &b =a; //这就是左值引用
int &&c = 10; //这就是右值引用
2.移动构造函数
看一个例子:
class demo
{
public:
demo():num(new int(0)) {
cout << "demo construct" << endl;
}
demo(const demo &d):num(new int(*d.num)) {
cout << "demo copy construct" << endl;
}
~demo() {
cout << "demo des_construct" << endl;
}
private:
int *num;
};
demo get_demo()
{
cout << "get_demo() func" << endl;
return demo();
}
int main() {
demo a = get_demo();
return 0;
}
测试结果:
$ make
g++ main.cpp -o app -fno-elide-constructors --std=c++11
$ ./app
get_demo() func //get_demo func的打印
demo construct //demo()创建了一个对象
demo copy construct //调用了return,demo()创建的对象拷贝到return 变量中
demo des_construct //demo()对象的析构
demo copy construct //a变量被函数返回的对象赋值,所以调用拷贝构造
demo des_construct //函数返回对象的析构
demo des_construct //a变量的析构
分析:
从上我们可以看出,创建变量a,调用了两次拷贝构造函数,并且都是深拷贝(深拷贝取决于是否有堆上的内存),这样的效率比较低
那么问题来了:
有没有效率更高的方式呢?
更为高效的方式:
其实是存在的,因为实际上我们分析可得上面的两次拷贝构造都是临时变量在拷贝构造,如果demo()所创建的对象能直接移动到a变量中,这样就不用再调用深拷贝了
因此我们来分析移动构造函数的写法:
我们只需要将临时对象的 num 指针直接浅拷贝给 a.num,然后修改该临时对象中 num 指针的指向(通常另其指向 NULL)
代码实例:
class demo
{
public:
demo():num(new int(0)) {
cout << "demo construct" << endl;
}
demo(const demo &d):num(new int(*d.num)) {
cout << "demo copy construct" << endl;
}
//添加了移动构造函数
demo (demo &&d):num(d.num) {
d.num = NULL;
cout << "demo move construct" << endl;
}
~demo() {
cout << "demo des_construct" << endl;
}
private:
int *num;
};
测试结果:
$ ./app
get_demo() func
demo construct
demo move construct
demo des_construct
demo move construct
demo des_construct
demo des_construct
可以看到,当为 demo 类添加移动构造函数之后,使用临时对象初始化 a 对象过程中产生的 2 次拷贝操作,都转由移动构造函数完成。
总结:
【由来】移动构造函数的效率更高,是为了解决拷贝构造函数的深拷贝问题;
【分析】程序执行结果中产生的临时对象(例如函数返回值、lambda 表达式等)既无名称也无法获取其存储地址,所以属于右值。可以使用移动构造函数来操作;
【使用顺序】当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用拷贝构造函数。