移动构造[1]
在现实中有很多这样的例子,我们将钱从一个账号转移到另一个账号,将手机SIM卡转移到另一台手机,将文件从一个位置剪切到另一个位置……移动构造可以减少不必要的复制,带来性能上的提升。
- C++11标准中提供了一种新的构造方法——移动构造。
- C++11之前,如果要将源对象的状态转移到目标对象只能通过复制。在某些情况下,我们没有必要复制对象——只需要移动它们。
- C++11引入移动语义:
- 源对象资源的控制权全部交给目标对象
移动构造与复制构造的差别:
![cfd2a7dedd4de876b3dae4fae99c06f4.png](https://i-blog.csdnimg.cn/blog_migrate/f67a41bbbcd0f02a399ae8c682a4f3cb.jpeg)
- 移动构造函数
如果临时对象即将消亡,并且它里面的资源需要被再利用的时候,就可以触发移动构造。
移动构造是要通过移动构造函数来完成。
- 什么时候该触发移动构造?临时对象即将消亡,且它里面的资源需要被再利用(有可被利用的临时对象)。
移动构造函数
class_name ( class_name && )
例:函数返回含有指针成员的对象(版本1)
- 使用深层复制构造函数
返回时构造临时对象,动态分配将临时对象返回到主调函数,然后删除临时对象。
#include<iostream>
using namespace std;
class IntNum {
public:
IntNum(int x = 0) : xptr(new int(x)) { //构造函数
cout << "Calling constructor..." << endl;
}
IntNum(const IntNum & n) : xptr(new int(*n.xptr)) {//复制构造函数
cout << "Calling copy constructor..." << endl;
};
~IntNum() { //析构函数
delete xptr;
cout << "Destructing..." << endl;
}
int getInt() { return *xptr; }
private:
int *xptr;
};
//返回值为IntNum类对象
IntNum getNum() {
IntNum a;
return a;
}
int main() {
cout << getNum().getInt() << endl;
return 0;
}
运行结果:
![f00a910fdf578e396aa03457432f0db0.png](https://i-blog.csdnimg.cn/blog_migrate/2f57d80c311452618be45685e769cedb.jpeg)
对程序的描述:
- IntNum类:用IntNum类存放一个指向整数的指针。
- 构造函数:在初始化列表中,动态构造一个整数变量,用来初始化这个指针xptr。动态构造的整数变量,返回一个指向Int的指针去初始化xptr。
- 复制构造函数:是深层复制。构造int对象,它的初始值是参数n的xptr指针所指向的对象值。这个深层复制的复制构造函数,构造了一个新对象int,构造成功以后,将新对象int的指针初始化当前对象的xptr。
- 析构函数:删除在构造函数中分配的内存空间,并输出提示信息。
- getInt()函数:返回指针所指向的值。
- getNum()函数:定义局部对象a,将局部对象作为结果返回 ,所以它的返回值是IntNum类型。
- 主函数:
- 主函数调用getNum()函数,构造一个IntNum对象a,调用构造函数,终端输出“Calling constructor..”;
- 调用getNum()函数后,返回一个IntNum对象a,a是一个非静态的局部变量,离开getNum函数以后它的寿命就结束了。return a的过程是构造一个临时无名对象返回到主函数中,终端输出“Calling copy constructor..”;
- 此时的IntNum对象a要被释放,即IntNum对象a的空间要被析构函数释放,终端输出"Destructing...";
- 此时主函数中调用getNum()函数得到的IntNum对象是临时无名对象,用此对象调用getInt()函数,cout输出它里面的值,终端输出“0”;
- 执行完cout语句后,临时无名对象就不再存在,终端输出"Destructing...";
使用移动构造的方法,可以省去构造临时无名对象,会简洁很多。
例:函数返回含有指针成员的对象(版本2)
- 使用移动构造函数
将要返回的局部对象转移到主调函数,省去了构造和删除临时对象的过程。
#include<iostream>
using namespace std;
class IntNum {
public:
IntNum(int x = 0) : xptr(new int(x)) { //构造函数
cout << "Calling constructor..." << endl;
}
IntNum(const IntNum & n) : xptr(new int(*n.xptr)) {//复制构造函数
cout << "Calling copy constructor..." << endl;
}
IntNum(IntNum && n) : xptr(n.xptr) { //移动构造函数
n.xptr = nullptr;
cout << "Calling move constructor..." << endl;
}
~IntNum() { //析构函数
delete xptr;
cout << "Destructing..." << endl;
}
int getInt() { return *xptr; }
private:
int *xptr;
};
//返回值为IntNum类对象
IntNum getNum() {
IntNum a;
return a;
}
int main() {
cout << getNum().getInt() << endl;
return 0;
}
运行结果:
![ca031f229618496cc28b11baacdabbbd.png](https://i-blog.csdnimg.cn/blog_migrate/78a0f5971fe3aa374b7aba1583608d1c.jpeg)
对程序的说明:
![7cad8e16faae254b0bb33a9bcb61b787.png](https://i-blog.csdnimg.cn/blog_migrate/5251c8cc611683a707fbe98e52451a6f.png)
移动构造函数: 直接用参数对象里面的指针来初始化当前对象的指针,是一种浅层复制。 做完这种指针对指针的复制,即把参数指针所指向的对象转给了当前正在被构造的这个指针,函数体内接着就把参数n里面的指针给置为空指针, 这个对象里面的指针置为空指针,将来析构函数去析构它的时候是delete一个空指针,就不会发生多次析构的事情,这个就是一个移动构造函数。
注意:
- &&是右值引用(即将消亡的值就是右值,函数返回的临时变量也是右值)
- &可以绑定左值(左值是指表达式结束后依然存在的持久对象)
主函数:
- 主函数调用getNum()函数,构造一个IntNum对象a,调用构造函数,终端输出“Calling constructor..”;
- 调用getNum()函数后,返回一个IntNum对象a,a是一个非静态的局部变量,离开getNum函数以后它的寿命就结束了。return a的过程,没有去调用复制构造函数,而调用了移动构造函数终,端输出"Calling move constructor...";
- 此时的IntNum对象a要被释放,即IntNum对象a的空间要被析构函数释放,但此时的对象a已经指向了空指针,它的资源通过指针初始化的方式转移了,终端输出"Destructing...";
- 此时主函数中调用getNum()函数得到的IntNum对象是的空间是原来的对象a的,用此对象调用getInt()函数,cout输出它里面的值,终端输出“0”;
- 执行完cout语句后,原来的对象a的空间也要被释放,终端输出"Destructing...";
移动构造效率要高一些,当遇到不需要构造那么多对象释放那么多对象的时候,可以将要消亡的对象的资源转移,这是效率高一些的办法。
参考
- ^http://www.xuetangx.com/courses/course-v1:TsinghuaX+00740043X_2015_T2+sp/courseware/d4eb7d174ba04a4da6282bcae197892c/328f49c8f22b4e8cb8fd3febf2ac8868/