c++移动构造函数
一.定义【源对象资源的控制权全部移交给目标对象】
有些复制构造是必要的,我们确实需要另外一个副本;而有些复制构造是不必要的,我们可能只是希望这个对象换个地方,移动一下而已。在C++11之前,如果要将源对象的状态转移到目标对象只能通过复制。而现在在某些情况下,我们没有必要复制对象——只需要移动它们。移动构造是C++11标准中提供的一种新的构造方法,移动构造可以减少不必要的复制,带来性能上的提升。
C++11引入移动语义:~源对象资源的控制权全部交给目标对象
移动构造函数定义形式:
class_name(class_name && ) //右值引用传参
移动构造函数中的参数类型,&&符号表示是右值引用;即将消亡的值就是右值,函数返回的临时变量也是右值,这样的单个的这样的引用可以绑定到左值的,而这个引用它可以绑定到即将消亡的对象,绑定到右值。
二. 拷贝构造与移动构造
- 复制构造:在对象被复制后临时对象和复制构造的对象独自都占有不同地址的堆内存,就是一个副本【深拷贝】
- 移动构造:是让这个临时对象它原本控制的内存的空间转移给构造出来的对象,这样就相当于把它移动过去了
这种情况下,我们觉得这个临时对象完成了复制构造后,就不需要它了,我们就没有必要去首先产生一个副本,然后析构这个临时对象,这样费两遍事,又占用内存空间,所幸将临时对象它的原本的资源直接转给构造的对象即可了。当临时对象在被复制后,就不再被利用了,我们完全可以把临时对象的资源直接移动,这样就避免了多余的复制构造。
三. 移动构造使用场景
3.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.xpr)) { cout<<"Calling copy constructor..."<<endl; } //移动构造函数 IntNum(IntNum && n):xptr(n.xptr) { n.xptr = nullptr; cout<<"Calling move constructor..."<<endl; } //析构函数 ~IntNum() { delete xpr; cout<<"Destructing..."<<endl; } int getInt(){return *ptr;}//返回指针所指向的值,而不是返回指针本身 private: int *ptr; }; //返回值为IntNum类对象 ,将亡值 IntNum getNum(){ //定义了一个局部对象,然后将局部对象作为结果返回 IntNum a; return a; } int main(){ //getNum()函数返回了一个IntNum类型的对象(临时无名对象),之后调用类的函数 cout<<"getNum().getInt()"<<endl; return 0; }
在该例中的移动构造函数:好像干了件很危险的事情:直接用参数对象(n)里面的指针(n.xptr)来初始化当前对象的指针(xptr),(按理说这不是浅层复制吗?!说了有指针成员,我们复制时不能做这种浅层复制的呀,怎么在这里我们恰恰做浅层复制了呢)
IntNum(IntNum && n):xptr(n.xptr){//移动构造函数 n.xptr = nullptr; cout<<"Calling move constructor..."<<endl; }
看函数体里面,我们发现再做完xptr(n.xptr)这种指针对指针的复制(也就是把参数指针所指向的对象转给了当前正在被构造的指针)后,接着就把参数n里面的指针置为空指针(n.xptr = nullptr;),对象里面的指针置为空指针后,将来析构函数析构该指针(delete xpr;)时,是delete一个空指针,不发生任何事情,这就是一个移动构造函数。
3.2 移动语义std::move()
由于在C++11中的移动构造函数的入参是右值引用,因此当传入左值时,无法调用该移动构造函数,需要借助move将左值转换成右值引用。move作用是可以将一个左值转换成右值引用,从而可以调用C++11的拷贝构造函数。
3.3 完美转发std::forward()
完美转发是指在函数模板中,完全依照模板的参数类型,将参数传递给当前函数模板中的另外一个函数。
因此,为了实现完美转发,除了使用万能引用之外,我们还要用到std::forward(C++11),它在传参的过程中保留对象的原生类型属性。这样右值引用在传递过程中就能够保持右值的属性。
void Func(int& x) { cout << "左值引用" << endl; } void Func(const int& x) { cout << "const左值引用" << endl; } void Func(int&& x) { cout << "右值引用" << endl; } void Func(const int&& x) { cout << "const右值引用" << endl; } template<typename T> void PerfectForward(T&& t) // 万能引用 { Func(std::forward<T>(t)); // 根据参数t的类型去匹配合适的重载函数 } int main() { int a = 4; // 左值 PerfectForward(a); const int b = 8; // const左值 PerfectForward(b); PerfectForward(10); // 10是右值 const int c = 13; PerfectForward(std::move(c)); // const左值被move后变成const右值 return 0; }
参考原文:
https://blog.csdn.net/sinat_25394043/article/details/78728504?ops_request_misc=&request_id=&biz_id=102&utm_term=%E7%A7%BB%E5%8A%A8%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-1-78728504.nonecase&spm=1018.2226.3001.4187