在c++11标准出来前,想用其它对象初始化一个同类新对象,只能借助类的拷贝构造函数。但是实际上可能会又多次拷贝,导致对象内部的指针成员每次深拷贝都会申请大量堆空间,势必会影响对象的初始化执行效率,然后就引入了移动构造函数,进行避免这样的无效多次拷贝。
看例子
#include <iostream>using namespace std;class demo{public:demo():num(new int(0)){cout<<"construct!"<<endl;}//拷贝构造函数demo(const demo &d):num(new int(*d.num)){cout<<"copy construct!"<<endl;}~demo(){cout<<"class destruct!"<<endl;}private:int *num;};demo get_demo(){return demo();}int main(){demo a = get_demo();return 0;}
执行时使用禁止优化命令 g++ demo.cpp -fno-elide-constructors
输出为
construct!copy construct!class destruct!copy construct!class destruct!class destruct!
可以看到先是 get_demo() 方法中的 demo() 执行了构造,然后赋给临时对象demo,这个过程进行了一次拷贝,然后demo() 对象执行了析构,然后临时对象赋值给 a 的时候,又执行了 拷贝。
如果我们添加一个移动构造函数,试试效果会如何
#include <iostream>using namespace std;class demo{public:demo():num(new int(0)){cout<<"construct!"<<endl;}//拷贝构造函数demo(const demo &d):num(new int(*d.num)){cout<<"copy construct!"<<endl;}//添加移动构造函数demo(demo &&d):num(d.num){d.num = NULL;cout<<"move construct!"<<endl;}~demo(){cout<<"class destruct!"<<endl;}private:int *num;};demo get_demo(){return demo();}int main(){demo a = get_demo();return 0;}
输出效果
construct!move construct!class destruct!move construct!class destruct!class destruct!
从之前的两次拷贝,换成了走移动构造函数,为什么呢?
当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用拷贝构造函数。
我们get_demo()返回的就是临时对象,这是一个右值。即右值来初始化对象将会调用移动拷贝构造函数。
这种写法的好处在于?
num指针会被深拷贝,每次走拷贝构造函数都要重新申请一个对空间,势必影响执行效率。
get_demo()方法创建的对象到我们使用的 a对象上,进过了2次拷贝,每次都深拷贝,有必要吗?没有。我们这块浅拷贝就行了,是不是?那就用移动拷贝构造函数,使用中需要避免同一块空间被释放多次的情况。
左值初始化对象如何调用移动构造函数?move
move 函数也是 c++11引入的新特性
直接看例子把
#include <iostream>using namespace std;class first {public:first() :num(new int(0)) {cout << "construct!" << endl;}//移动构造函数first(first &&d) :num(d.num) {d.num = NULL;cout << "first move construct!" << endl;}public: //这里应该是 private,使用 public 是为了更方便说明问题int *num;};class second {public:second() :fir() {}//用 first 类的移动构造函数初始化 firsecond(second && sec) :fir(move(sec.fir)) {cout << "second move construct" << endl;}public: //这里也应该是 private,使用 public 是为了更方便说明问题first fir;};int main() {second oth;second oth2 = move(oth);//cout << *oth.fir.num << endl; //程序报运行时错误return 0;}
输出结果
construct!first move construct!second move construct
oth 为左值,想要调移动构造,必须通过move生一个oth的右值版本。
oth对象内包含了一个first对象,对于first来说也是左值,如果first也想要调用移动,那么也要调用move生成一个右值版本
