Bootstrap

移动构造函数_C++移动构造(学习笔记:第6章 21)

移动构造[1]

在现实中有很多这样的例子,我们将钱从一个账号转移到另一个账号,将手机SIM卡转移到另一台手机,将文件从一个位置剪切到另一个位置……移动构造可以减少不必要的复制,带来性能上的提升。

  • C++11标准中提供了一种新的构造方法——移动构造。
  • C++11之前,如果要将源对象的状态转移到目标对象只能通过复制。在某些情况下,我们没有必要复制对象——只需要移动它们。
  • C++11引入移动语义:
    • 源对象资源的控制权全部交给目标对象

移动构造与复制构造的差别:

cfd2a7dedd4de876b3dae4fae99c06f4.png
  • 移动构造函数

如果临时对象即将消亡,并且它里面的资源需要被再利用的时候,就可以触发移动构造。

移动构造是要通过移动构造函数来完成。

  • 什么时候该触发移动构造?临时对象即将消亡,且它里面的资源需要被再利用(有可被利用的临时对象)。

移动构造函数

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

对程序的描述:

  1. IntNum类:用IntNum类存放一个指向整数的指针。
  2. 构造函数:在初始化列表中,动态构造一个整数变量,用来初始化这个指针xptr。动态构造的整数变量,返回一个指向Int的指针去初始化xptr。
  3. 复制构造函数:是深层复制。构造int对象,它的初始值是参数n的xptr指针所指向的对象值。这个深层复制的复制构造函数,构造了一个新对象int,构造成功以后,将新对象int的指针初始化当前对象的xptr。
  4. 析构函数:删除在构造函数中分配的内存空间,并输出提示信息。
  5. getInt()函数:返回指针所指向的值。
  6. getNum()函数:定义局部对象a,将局部对象作为结果返回 ,所以它的返回值是IntNum类型。
  7. 主函数:
  • 主函数调用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

对程序的说明:

7cad8e16faae254b0bb33a9bcb61b787.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...";

移动构造效率要高一些,当遇到不需要构造那么多对象释放那么多对象的时候,可以将要消亡的对象的资源转移,这是效率高一些的办法。

参考

  1. ^http://www.xuetangx.com/courses/course-v1:TsinghuaX+00740043X_2015_T2+sp/courseware/d4eb7d174ba04a4da6282bcae197892c/328f49c8f22b4e8cb8fd3febf2ac8868/
;