右值引用

右值引用的由来

看个例子:

#include <vector>
#include <iostream>
#include <string.h>

class String {
public:
	String();
	String(const char* str);
	String(const String& rhs);

	String& operator=(const String& rhs);
	~String();

	friend std::ostream& operator<<(std::ostream& os, const String& rhs);
private:
	char* _pstr;
};

String::String()
	:_pstr(new char[1]())
{
	std::cout << "String::String()" << std::endl;
}

String::String(const char* str)
{
	std::cout << "String::String(const char* str)" << std::endl;
	_pstr = new char[strlen(str) + 1]();
	strcpy(_pstr, str);
}

String::String(const String& rhs)
{
	std::cout << "String::String(const String& rhs)" << std::endl;
	_pstr = new char[strlen(rhs._pstr) + 1]();
	strcpy(_pstr, rhs._pstr);
}

String& String::operator=(const String& rhs)
{
	if (this == &rhs) {
		delete[]_pstr;
		_pstr = new char[strlen(rhs._pstr) + 1];
		strcpy(_pstr, rhs._pstr);
	}
	return *this;
}

String::~String()
{
	std::cout << "String::~String()" << std::endl;
	if (_pstr != NULL) {
		delete[]_pstr;
	}
}

std::ostream& operator<<(std::ostream& os, const String& rhs)
{
	os << rhs._pstr;
	return os;
}

int main()
{
	std::vector<String> vecInt;
	vecInt.push_back("hello,world");
	std::cout << vecInt[0] << std::endl;
}

执行结果如图
右值引用
std::vector vecInt;
vecInt.push_back(“hello,world”);
可以看到这两句代码实际发生了这三个步骤:
①调用String::String(const char *); 通过hello,world构造一个临时对象
②String::String(const String&); 第一步是临时对象,放到容器中需要左值,因此要通过第一步构造的string对象调用复制构造函数构造出一个持久化的对象放到容器中
③String::~String()

这里产生了两次构造和析构。问题就在于临时对象的构造和析构带来了不必要的资源拷贝,有什么办法可以在第②步识别出临时对象,将临时对象的资源转移到新的对象中,消除不必要的拷贝,因此引入了右值引用。

const引用小知识点

const int& m = a; // 常量引用可以绑定到左值
const int& n = 123; // 常量引用也可以绑定到右值
int& p = a;  
int& k = 123;// 编译报错,非常量引用的初始值必须为左值

因此上面的例子中String::String(const String&rhs) ,复制构造函数内部无法识别出rhs是左值还是右值,C++11引入右值引用,例:int& k = 123 改写成为 int&& k = 123
ps:常量右值引用没有实际意义,右值引用的初衷在于移动语义,而移动就意味着修改

具有移动语义的string

基于上面例子新增两个函数 移动复制构造函数和移动赋值运算符函数

#include <vector>
#include <iostream>
#include <string.h>

class String {
public:
	String();
	String(const char* str);
	String(const String& rhs);
	// 移动构造函数
	String(String&& rhs);

	String& operator=(const String& rhs);
	// 移动赋值运算符函数
	String& operator=(String&& rhs);
	~String();

	friend std::ostream& operator<<(std::ostream& os, const String& rhs);
private:
	char* _pstr;
};

String::String()
	:_pstr(new char[1]())
{
	std::cout << "String::String()" << std::endl;
}

String::String(const char* str)
{
	std::cout << "String::String(const char* str)" << std::endl;
	_pstr = new char[strlen(str) + 1]();
	strcpy(_pstr, str);
}

String::String(const String& rhs)
{
	std::cout << "String::String(const String& rhs)" << std::endl;
	_pstr = new char[strlen(rhs._pstr) + 1]();
	strcpy(_pstr, rhs._pstr);
}

String& String::operator=(const String& rhs)
{
	if (this == &rhs) {
		delete[]_pstr;
		_pstr = new char[strlen(rhs._pstr) + 1];
		strcpy(_pstr, rhs._pstr);
	}
	return *this;
}

String::~String()
{
	std::cout << "String::~String()" << std::endl;
	if (_pstr != NULL) {
		delete[]_pstr;
	}
}

String::String(String&& rhs)
{
	std::cout << "String::String(String&& rhs)" << std::endl;
	_pstr = rhs._pstr;
	rhs._pstr = NULL; // 注意这里不要忘了
}

String& String::operator=(String&& rhs)
{
	std::cout << "String& String::operator=(String&& rhs)" << std::endl;
	if (this == &rhs) {
		delete []_pstr;
		_pstr = rhs._pstr;
		rhs._pstr = NULL; // 注意这里不要忘了
	}
	return *this;
}

std::ostream& operator<<(std::ostream& os, const String& rhs)
{
	os << rhs._pstr;
	return os;
}

int main()
{
	std::vector<String> vecInt;
	vecInt.push_back("hello,world");
	std::cout << vecInt[0] << std::endl;
}

执行结果如下,可以看到helloworld产生的临时对象申请的空间直接转移给了新对象:
右值引用
注意:一定不要忘了给临时对象赋空,不然临时对象销毁时就把该空间释放了。

参考资料:
https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
http://thbecker.net/articles/rvalue_references/section_01.html
https://www.jianshu.com/p/d19fc8447eaa
https://zhuanlan.zhihu.com/p/107445960
https://zhuanlan.zhihu.com/p/99524127
《深入理解C++11》

上一篇:[CF1182E] Product Oriented Recurrence - 矩阵快速幂


下一篇:第一章 监控系统入门