您好,欢迎来到华拓科技网。
搜索
您的当前位置:首页[C++] C++11详解 (二)右值引用与移动语义

[C++] C++11详解 (二)右值引用与移动语义

来源:华拓科技网

标题:[C++11] 右值引用与移动语义

@水墨不写bug




正文开始:

一、C++11的新增语法:右值引用

        引用是C++中传统的语法,而C++11中新增了右值引用的语法,因此,为了区分,从现在开始,我们之前学的引用称为左值引用。无论是左值引用还是右值引用,都是给对象取别名。

1.左值与左值引用

a.简介


b.在语法上:

普通引用(左值引用)

  • 语法形式:类型& 引用名 = 初始值;
  • 示例:int a = 10; int& ref = a;
  • 特点:
    • 必须在声明时初始化。
    • 只能绑定到左值上。
    • 相当于变量的别名,不占用额外内存空间。
#include<iostream> 
using namespace std;
int main()
{
	//p,b,c都是左值
	int* p = new int[3] {0};
	int b = 3;
	const int c = 1;

	//rp,rb,rc是对左值的引用
	int*& rp = p;
	int& rb = b;
	const int& rc = c;


	return 0;
}

c.常见的应用场景:

普通引用(左值引用)

  • 作为函数调用时传入参数,避免拷贝,提高效率
  • 作为函数返回值,可以返回对象的引用(但需保证引用对象在函数返回后依然有效)。


2.右值与右值引用

a.简介

        右值也是一个表示数据的表达式。如:字面常量、表达式返回值,函数返回值(不能是左值引用返回)等等。

#include<iostream> 
using namespace std;
int main()
{
	double x = 1.0;
	int y = 5;
	
	//一下是常见的右值
	19;
	x + y;
	fmax(x, y);

	//一下是对右值的引用
	int&& r1 = 19;
	double&& r2 = x + y;
	double&& r3 = fmax(x,y);

	//右值无法出现在赋值符号的左边
	//编译报错:左操作数必须为可修改的左值
	10 = 1;
	x + y = 10;
	fmax(x, y) = 0;

	return 0;
}

b.语法

右值引用(也称为移动语义引用)

  • 语法形式:类型&& 引用名 = 初始值;
  • 示例:int&& rref = std::move(a);
  • 特点:
    • 必须在声明时初始化。
    • 可以绑定到临时对象(右值)上。
    • 允许修改绑定的对象(这就是说,在右值引用之后,引用本身退化为左值)。

c.常见应用场景

右值引用
  • 实现移动语义和完美转发,提高资源管理的效率。
  • 用于编写高效的 移动赋值运算符重载移动构造函数
  • 在模板编程和泛型编程中,右值引用允许函数模板接受右值作为参数,进而实现移动语义。

 此外,补充注意:

        左值引用可以引用右值,但是前提是需要将左值引用类型声明为const;

        右值引用也可以引用move之后的左值。 


3、左值引用与右值引用总结

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

右值引用总结:
        1. 右值引用只能右值,不能引用左值。
        2. 但是右值引用可以move以后的左值。

二、移动语义

        其实,你一定早已发现:右值代表的是将亡值,它的生命周期将要到达尽头。而左值就是我们创建的一般变量。

1.移动构造 

        在下面这几个场景中,你会发现使用拷贝构造会很憋屈,这也就是移动构造要解决的问题。 


        场景一: 在给map的insert传递参数时,我们需要传递一个pair类型的参数,但是为了传递参数而专门创建一个对象,这就很麻烦。在这样的情况下我们通常选择传递一个临时对象的方法来解决。但是,传临时对象,也会有需要面临的问题:需要把临时对象深拷贝到map中。

        为了避免深拷贝,我们可以把右值的数据交换到map中,这样就减少了拷贝。这就引出了移动语义。

        场景二:一个函数需要传递一个局部变量返回,由于局部变量出了作用域就会销毁,所以只能传值返回,那么这样一般就需要两次拷贝:局部变量拷贝给临时对象,临时对象拷贝给接受返回值对象,非常浪费资源。

        在没有实现移动构造时,str会被识别为右值,但是编译器没有选择,只能使用深拷贝传值返回。但是在实现了移动构造之后,编译器会采用更加适合的方式:调用移动构造。

        为了能看清楚移动构造内部的情况,我们使用自己提前写好的string;移动构造参数类型为右值引用,传入的参数是一个将亡值,在函数内部,构造*this,然后需要把参数将亡值与*this的数据交换即可,实现如下:

// 移动构造
string(string&& s)
    :_str(nullptr)
    ,_size(0)
    ,_capacity(0)
{
    swap(s);
}

 2.其他移动语义:移动赋值

         在解释清楚移动构造之后,移动赋值就十分清晰了:函数参数为右值引用;在函数内部实现*this与将亡值的交换:

// 移动赋值
string& operator=(string&& s)
{
    swap(s);
    return *this;
}

移动语义的意义: 

        左值引用做参数和做返回值都可以提高效率;但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。
        移动构造的出现是为了实现减少拷贝,把将亡值的数据移动给目的处的赋值对象,移动构造中没有开辟新空间,拷贝数据,所以效率提高了。


完~

未经作者同意禁止转载 

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- huatuo6.cn 版权所有 赣ICP备2024042791号-9

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务