C++11中右值引用与移动构造函数

目录:

左值和右值


早期C语言给出的定义是:左值是一个表达式,可以出现在=的左边或右边;但右值只能出现在右边。

临时对象是右值

void leftValueAndRightValue()
{
	int nLeftValue = 10;	// nLeftValue是左值,10是右值
	int* nP = &nLeftValue;	// nLeftValue是左值
	int fun();
	int nResult = 0;
	nResult = fun();	// 此行中fun是右值
	int* nP1 = &fun();  // 错误,表达式必须为左值,不能获取右值的地址
}

左值引用和右值引用


c++11之前只有左值引用,也就是常用的引用&

例如:

// 此行中nLeftValue是左值,nLeftReference是左值引用
int& nLeftReference = nLeftValue;

右值引用 是 C++ 11中引入的新特性 ,可以理解为对右值的引用,我们知道在C++中右值不能被修改。但右值引用的出现,打破了这个限制,它允许我们获取右值的引用

右值引用的形式为:类型 && a= 被引用的对象

此外需要注意的是右值引用只能绑定到右值

例如:

int nLeftValue = 10;	// nLeftValue是左值,10是右值
int&& nRightReference = 10;
int&& nRightErrRef = nLeftValue;	//错误: 无法将右值引用绑定到左值

引用作为函数参数


C++中的(左值)引用可以用作函数的参数,并且也建议尽可能用引用作为函数的参数,主要原因是传引用比传值效率更高。其实不光左值引用可以作为函数参数,在C++中,右值引用也能作为函数参数

例如:

void testRefParam(int& param)
{
	cout << "left ref param" << endl;
}
void testRefParam(int&& param)
{
	cout << "right ref param" << endl;
}
int main()
{
	int nTest = 10;
	testRefParam(nTest); // 输出:left ref param
	testRefParam(10);	// 输出:right ref param
	system("pause");
	return 0;
}

左值引用只能绑定左值(const左值引用除外)

右值引用只能绑定右值

构造函数的局限性


我们定义了一个工厂函数获得Test对象,然后在main()函数中创建了一个Test对象 t ,然后将调用工厂函数获得Test对象初始化 t ,运行程序

例如:

class Test
{
public:
	Test() :m_p(new int[100]{10,20,30})
	{
		cout << "default constructor" << endl;
	}
	~Test()
	{	
		cout << "deconstructor" << endl;
		if (m_p)
		{
			delete m_p;
			m_p = nullptr;
		}
	}
	Test(const Test& param)
	{
		cout << "copy constructor" << endl;
		m_p = new int[100];
		memcpy(m_p, param.m_p, 100 * sizeof(int));
	}
private:
	int *m_p{ nullptr };
};
Test instance()
{
	// 函数返回时调用了拷贝构造函数
	return Test();
}
int main()
{
	{
		// 用一个对象初始化另一个对象时调用了拷贝构造函数
		Test t(instance());
	}
	system("pause");
	return 0;
}

只是输出了:

default constructor
deconstructor

为什么没有按照预想的输出两次

copy constructor

究其原因是因为编译器已经为我们做了优化,具体怎么做的优化可以参考这篇文章

这个示例中调用了两次拷贝构造函数构造了两个临时对象,一次是在instance函数返回时,一次在对t的初始化。其实这两个临时对象并没有什么意义,构造完了马上就析构了。但是就是因为这么两个无用的东西,在拷贝构造函数中执行了不必要的内存拷贝,这里还好,只是拷贝了100个int类型的对象,如果是拷贝100000个甚至更多了,可想而知效率是多么的低了。

移动构造函数


有没有可能将 在工厂函数当中所构造对象的成员变量(m_p)所指向的那块内存“偷”过来,而不是重新开辟一块内存,然后再将之前的内容复制过来呢? 这就是移动构造函数设计的思想

所谓移动构造函数,大家从名字上应该可以猜到:它应该就是一种构造函数,只不过它接受的参数是一个本类对象的右值引用,对于本例,移动构造函数的定义如下:

Test(Test&& moveParam):m_p(moveParam.m_p)
{
	cout << "move constructor" << endl;
	moveParam.m_p = nullptr;
}

在移动构造函数的初始化列表中,只做了一个浅拷贝m_p(moveParam.m_p),将moveParam对象已经申请的内存据为己用,同时将moveParam的指针赋值为nullptr。这就避免了拷贝构造函数内存复制导致的效率问题

移动构造函数在用来构造临时变量或者用临时变量来构造对象的时候被调用

std::move


std::move来了

现在我们来看另外一种场景,在下面的情况下,我们知道在Test t2(t1)处会调用拷贝构造函数(t1是左值,因此不会调用移动构造函数),那么有没有一种办法在此处也调用移动构造函数而不是拷贝构造函数呢?

{
	Test t1;
	Test t2(t1);
}

C++11标准中给我们提供了std::move来解决这个问题,如下,只需将Test t2(t1)换成下面的语句即可:

Test t2(std::move(t1));

这个std::move的作用就是将左值转换为右值,以便调用移动构造函数。这里有一点特别需要注意的是,在调用std::move语句后,不能再对原对象进行操作了

除非另外指定,否则所有已被移动的标准库对象被置于合法但未指定的状态。即只有无前提的函数,例如赋值运算符,才能安全地在对象被移动后使用

参考文章


https://mp.weixin.qq.com/s/2_y3NgMH6b8bTKZ8pZTuew