【C++11】右值引用和移动语义

一、 左值和左值引用

1. 左值

——表达数据的表达式(变量名或解引用的指针),可以被获取地址且可以对其进行赋值(可以修改值)。

左值既可出现在赋值符号("=")的左侧也可出现在其右侧。

2. 左值引用

引用是给对象取别名,左值引用即给左值对象取别名——共享资源

int main()
{
	// 以下的p、b、c、*p都是左值
	int* p = new int(0);
	int b = 1;
	const int c = 2;
	// 以下几个是对上面左值的左值引用
	int*& rp = p;
	int& rb = b;
	const int& rc = c;//注意引用的权限问题
	int& pvalue = *p;//pvalue是*p的引用
	pvalue = 4;
	cout << *p << endl;

	return 0;
}

二、右值和右值引用

1. 右值

——表达数据的表达式(内置常量、表达式返回值、函数返回值、匿名对象等),不可被获取地址也不可对其赋值(值不可被修改)右值包括两大类:内置类型常量、将亡值。

右值只可出现在赋值符号("=")的右侧。

2. 右值引用

引用是给对象取别名,右值引用即给右值取别名,但是由于右值本质上并未实际存储,所以右值引用是将右值的资源存储到特定的位置。所以,右值虽不可被取地址,但是右值引用变量可以,右值引用变量是左值,可取地址也可进行值的修改。如果不希望修改值,可以加const修饰。

int main()
{
	double x = 1.1, y = 2.2;
	int&& rr1 = 10;
	const double&& rr2 = x + y;
	//如上,rr1和rr2均是右值引用,均是左值;
	//rr1可被取地址可被修改,rr2由于加了const修饰,所以可以取地址但是不可被修改
	rr1 = 20;
	//rr2 = 5.5;  // 报错
	return 0;
}

三、左值引用 vs 右值引用

1. 左值引用总结

  1. 左值引用只能引用左值,不能引用右值。
  2. 但是const左值引用既可引用左值,也可引用右值。

2. 右值引用总结

  1. 右值引用只能右值,不能引用左值。
  2. 但是右值引用可以move以后的左值。
int main()
{
    //1.1 左值引用只能引用左值,不能引用右值。
    int a1 = 10;
    int& ra1 = a1;   // ra为a的别名
    //int& ra2 = 10;   // 编译失败,因为10是右值

    //1.2 const左值引用既可引用左值,也可引用右值。
    const int& ra3 = 10;
    const int& ra4 = a1;

    //2.1 右值引用只能右值,不能引用左值。
    int&& r1 = 10;

    // error C2440: “初始化”: 无法从“int”转换为“int &&”
    // message : 无法将左值绑定到右值引用
    int a2 = 10;
    //int&& r2 = a2;

    //2.2 右值引用可以引用move以后的左值
    int&& r3 = std::move(a2);
    cout <<"a2: " << a2 << endl;

    return 0;
}

3. 右值引用的使用场景

C++98中已经有既能引用左值也能引用右值的左值引用了(即之前学习的引用),为何还要C++11增加右值引用的设计呢???——左值引用想要引用右值必须加const关键字,则引用后无法对左值引用进行改变;而右值引用虽然引用的是右值(本质就是一个常量值或将亡值),但是右值引用自己是一个左值(可以被取地址也可以被修改)。因此,右值引用的作用。如下会详细进行说明在某些特定的场景下有特别重要:

3.1左值引用&右值引用的场景&左值引用短板

  1. 左值引用直接减少拷贝(共用资源):1)左值引用传参 2)引用返回(返回后仍在作用域内不被释放的资源)
//1.1 拷贝构造
 string(const string& s)
 		:_str(nullptr)
 {
 		cout << "string(const string& s) -- 深拷贝" << endl;
 		string tmp(s._str);
 		swap(tmp);
 }

//1.2 赋值重载
 string& operator=(const string& s)
 {
		cout << "string& operator=(string s) -- 深拷贝" << endl;
 		string tmp(s);
 		swap(tmp);
 		return *this;
 }
  1. 左值引用返回的短板: 但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回, 只能传值返回。
  2. 右值引用间接减少拷贝(转移资源):2)移动构造(可减少拷贝) 2)局部变量返回(编译器先转移资源再释放资源)

3.2 移动构造 & 移动赋值

移动构造本质是将参数右值的资源窃取过来,占为已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。

//1. 移动构造
string(string&& s)//右值引用,可对右值进行引用;可被修改,可以直接与this的内容进行交换
	:_str(nullptr)
	, _size(0)
	, _capacity(0)
{
	cout << "string(string&& s) -- 移动语义" << endl;
	swap(s);
}

//2. 移动赋值
string& operator=(string&& s)
{
	cout << "string& operator=(string&& s) -- 移动语义" << endl;
	swap(s);
	return *this;
}

3.3 传值返回

1.未定义移动构造:

2.定义移动构造后:

str属于将亡值,编译器会将其识别为右值,函数返回时,会先转移资源(移动构造),再释放资源。中间的临时变量也属于将亡值,编译器再度将其视为右值,进行移动构造,得到ret2。综上,需要两次移动构造(只有资源移动);较新的编译器会将其优化成一次移动构造。( 移动构造中没有新开空间也没有拷贝数据,所以效率提高了。)

但如果ret2是已经存在的对象,则需要调用一次移动构造+一次移动赋值,这种情况编译器不能优化。

3.4 move()后实现移动语义

按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能 真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过move 函数将左值转化为右值。C++11中,std::move()函数位于 头文件中, 它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。

int main()
{
	bit::string s1("hello world");//默认构造
	
	bit::string s2(s1);//拷贝构造
	// 这里我们把s1 move处理以后, 会被当成右值,调用移动构造
	// 但是这里要注意,一般是不要这样用的,因为我们会发现s1的资源被转移给了s3,s1被置空了。
	bit::string s3(std::move(s1));
	return 0;
}

由上可知:move()可以短暂的使编译器将左值识别为右值,以便实现移动语义(右值引用实现资源转移),但是由于转移后左值的值会被置换,所以move()要谨慎使用。

综上,右值引用存在的意义在于将内置类型常量或将亡值引用后与其他值进行资源交换,以达到减少拷贝的作用,从而提高性能。

四、万能引用

模板中的&&是万能引用(引用折叠),可以是左值引用也可以是右值引用,由实参的类型决定。

void Fun(int& x)
{ 
	cout << "左值引用" << endl; 
}

void Fun(const int& x) 
{ 
	cout << "const 左值引用" << endl;
}

void Fun(int&& x) 
{ 
	cout << "右值引用" << endl; 
}

void Fun(const int&& x) 
{ 
	cout << "const 右值引用" << endl; 
}

// 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
// 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
// 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,
// 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发
template<typename T>
void PerfectForward(T&& t)//万能引用
{
	Fun(t);//t不论是左值引用还是右值引用,它自己一直是左值
}
int main()
{
	PerfectForward(10);           // 右值
	int a;
	PerfectForward(a);            // 左值
	PerfectForward(std::move(a)); // 右值
	const int b = 8;
	PerfectForward(b);      // const 左值
	PerfectForward(std::move(b)); // const 右值
	return 0;
}

应用:完美转发。

五、emplace系列

emplace操作是C++11新特性,新引入的的三个成员emlace_front、empace 和 emplace_back,这些操作构造而不是拷贝元素到容器中,这些操作分别对应push_front、insert 和push_back,允许我们将元素放在容器头部、一个指定的位置和容器尾部。

emplace系列在某些场景下比push_front、insert 和push_back更高效,不过大部分场景没什么区别。——可以无脑使用emplace系列函数进行元素的插入。

优势——可以直接构造,对于深拷贝的类效果不显著,但是对于浅拷贝的类有一些效果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值