欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

c++11学习笔记(4)- 移动构造函数与移动语义

程序员文章站 2022-03-22 20:56:12
...

1.移动构造函数

什么是移动构造函数呢?
我们首先来看一个例子:

#include <iostream>
#include <stdlib.h>

class Object
{
public:
	Object() {
		std::cout << "Object()" << std::endl;
		m_pMem = new char[10];
		memset(m_pMem, 0, 10);
	}
	~Object(){
		delete m_pMem;
	}

	Object(const Object& object) {
		std::cout << "Object(const Object&)" << std::endl;
		m_pMem = new char[10];
		memcpy(m_pMem, object.m_pMem, 10);
	}

	Object getObject(void) {
		Object obj;
		return obj;
	}

private:
	char* m_pMem = nullptr;
};

int main(int argc, char** argv)
{
	Object o1;
	Object o2 = o1.getObject();

	system("pause");
}

该示例的运行结果为:
Object()
Object()
Object(const Object&)

代码main()函数中 Object o1; 执行了一次普通构造函数;函数 getObject 也执行了一次普通构造函数并生成一个临时的 Object 对象,这里由于编译器的优化,这个临时对象就是函数中的obj 本身; o2 对象的创建执行了一次拷贝构造函数。

如果我们能够把函数 getObject 产生的临时对象直接赋值给 o2 那该有多好啊,这样就可以不用使用拷贝构造再重新生成内存了,提升了程序的效率。那么有没有什么办法实现呢,当然是有的,使用移动构造函数,我们在上面的类 Object 中添加如下代码:

Object(Object&& object) {
	std::cout << "Object(Object&& object)" << std::endl;
	m_pMem = object.m_pMem;
	object.m_pMem = nullptr;
}

程序的运行结果如下:
Object()
Object()
Object(Object&& object)

使用移动构造函数 Object(Object&& object) 将临时对象的堆内存转移到对象o2中,并使用代码object.m_pMem = nullptr; 以便object临时对象能够安全的被析构。
Object&& 实际上上一个 右值引用 ,关于右值引用可以参考上一篇文章
左值、右值和右值引用

在C++中这样“偷走”临时变量中资源的构造函数,就叫做 移动构造函数 ;而这样“偷”的行为就叫做 移动语义。移动语义在资源共享时,可以起到关键的作用。

2. std::move

在c++11中,标准库在<utility> 中提供了一个有用的函数 std::move,这个函数并不移动任何东西,他唯一的功能是将一个左值强制转化为右值引用,以用于移动语义

值得一提的是,被转化的左值,其生命周期并没有随着左右值得转化而改变。

我们将上面的代码改写一下:

#include <iostream>
#include <stdlib.h>

class Object
{
public:
	Object() {
		std::cout << "Object()" << std::endl;
		m_pMem = new char[10];
		memset(m_pMem, 0, 10);
	}
	~Object(){
		delete m_pMem;
	}

	Object(const Object& object) {
		std::cout << "Object(const Object&)" << std::endl;
		m_pMem = new char[10];
		memcpy(m_pMem, object.m_pMem, 10);
		m_Number = object.m_Number;
	}

	Object(Object&& object) {
		std::cout << "Object(Object&& object)" << std::endl;
		m_pMem = object.m_pMem;
		object.m_pMem = nullptr;
		m_Number = object.m_Number;
	}

	Object getObject(void) {
		Object obj;
		return obj;
	}

	int m_Number = 10;

private:
	char* m_pMem = nullptr;
};

int main(int argc, char** argv)
{
	Object o1;
	Object o2 = std::move(o1);

	std::cout << o1.m_Number << std::endl;

	system("pause");
}

运行结果为
Object()
Object(Object&& object)
10

可见 o1 并没有因为左右值得转换而被释放。

有了移动语义,我们可以实现一个高效率的置换(swap)函数,代码如下:

template<typename T>
void swap(T& t1, T& t2) 
{
	T temp(std::move(t1));
	t1 = std::move(t2);
	t2 = std::move(temp);
}

如果T是可移动的,那么移动构造和移动赋值将用于这个替换,提高交换的效率;如果T是不可移动的确是可拷贝的,那么拷贝语义将会用于这个置换。