C++11引入了右值引用,它也是C++11最重要的新特性之一。原因在于它解决了C++的一大历史遗留问题,即消除了很多场景下的不必要的额外开销。即使你的代码中并不直接使用右值引用,也可以通过标准库,间接地从这一特性中收益。为了更好地理解该特性带来的优化,以及帮助我们实现更高效的程序,我们有必要了解一下有关右值引用的意义。

什么是右值引用

右值

在引入右值的概念前,我们不妨先看看左值。一句话加以概括:左值就是等号左边的值;同理,右值也就是等号右边的值。举个例子:int a = 2;

这里的a是等号左边,可以通过取址符&来获取地址,所以是一个左值。而5在等号右边,无法通过取址符&来获取地址,所以只一个右值。

右值引用

左值引用是对于左值的引用或者叫别名。同样地,右值引用也就是对于右值引用。语法也很简单,就是在左值引用的语法之上在多加一个&,写成类型 &&右值引用名 = 右值;的形式即可,比如:

int &&a = 5;
a = 6;
string s1 = "hello";
string &&s2 = s1 + s1;
s2 += s1;

上述简单例子,展示了右值引用的基本用法。不过通常情况下,右值引用更多的是被用于处理函数参数。比如:

struct Student {
Student(Student &&s);
};

为什么要使用右值引用

C++11之前,很多C++程序里存在大量的临时对象,又称无名对象。主要出现在如下场景:

  • 函数的返回值
  • 用户自定义类型经过一些计算后产生的临时对象
  • 值传递的形参

先说函数的返回值,最常见的类型就是某些返回用户自定义类型的时候,如果没有将其复制,就会产生临时对象,比如:

Student func1();
// 返回一个Student对象...func1();
// 调用了func1创建了一个Student对象,但是没有使用,于是编译器创建了一个临时对象来进行存储

然后是某些计算操作后产生的临时对象,比如:

Complex result = c1 + c2 + c3;
// 编译器先计算c1 + c2的结果,并产生一个临时对象temp来存储结果,然后计算temp + c3的结果,然后将结果复制给result

还有值传递的方式的形参,例如:

void func(Student s);
// 值传递...Student stu;func(stu);
// 这里相当于是做了一次复制操作 Student s(stu);

而且这些临时对象随着生命周期的结束,编译器还会调用一次析构函数。随着这些操作次数的增加,或者当临时变量是个很大的类型时,这无疑会极大提高程序的开销,从而降低程序的效率。

C++11之后,随着右值引用的出现,可以有效的解决这些问题。通过move移动构造移动赋值运算符函数来获得临时对象的所有权,从而避免拷贝带来的额外开销,提高程序效率

移动构造

我们都知道,由于C++11之前,如果没有手动声明,编译器会给一个用于自定义类型(包括classstruct)自动生成的4个函数,分别是构造函数,拷贝构造函数,赋值运算符重载函数和析构函数。虽然通过传引用的方式,可以避免对象的复制。但是还是没法避免上述的临时对象的复制。而移动语义成功的解决的这个问题。

C++11之后,编译器自动生成的函数中又新增了2个,它们就是移动构造移动赋值运算符重载函数,通过它们,我们可以很好地实现对用户自定义类型的移动操作。而移动的本质就是获取临时对象的所有权,而不是通过复制的方式来获得。直接看代码:

class Foo {
public:
Foo(Foo &&rhs) : ptr_(rhs.ptr_) { rhs.ptr_ = nullptr; }
Foo &operator=(Foo &&rhs) {
if (*this != rhs) {
ptr_ = rhs.ptr_;
rhs.ptr_ = nullptr;
}
return *this;
} private:
int *ptr_;
};

Foo类重载了移动构造函数和移动赋值运算重载函数,使得Foo获得了移动的能力,当我们在面对产生临时的对象的时候,编译器就会根据传入的参数是左值还是右值来选择调用拷贝还是移动。如果是右值,就调用移动构造或移动赋值运算符函数。当Foo是一个很大的对象时候,就会极大的降低开销,提高程序效率。

move的应用场景

通过上述例子,我们可以看到移动并不是说完全没有开销,甚至有的时候开销并不一定比拷贝低,具体还是要看临时对象的大小和类型决定,例如:

vector<vector<int>> func() {
vector<vector<int>> res;
for (...) {
vector<int> temp;
// 没必要直接传就可以了
temp.emplace_back(move(5));
// 可以,替代了拷贝操作,提高了效率
res.emplace_back(move(res));
}
return res;
}

STL的大部分组件都支持移动语义,比如vectorstring等即可以通过move转换右值后调用移动构造函数进行移动操作来避免深拷贝。还有一些类是只允许移动,不允许拷贝,从而更让设计更符合逻辑,比如unique_ptr

move的原理

move函数的源码并不复杂:

template <class _Ty>
inline _CONST_FUN typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT {
return (static_cast<typename remove_reference<_Ty>::type&&>(_Arg));
}

我们可以一眼看到,move的实现其实就做了一件事,如果是左值,就通过static_cast将传进来的参数强转为右值并返回;如果是右值,甚至不用转换,直接返回。

右值移动的注意事项

  • 和左值移动一样,都需要直接初始化
  • 右值引用无法指向左值,除非使用move将其转成右值,否则编译报错
  • 当对象是基本类型的时候,没必要调用move,因为拷贝的开销可能还不如函数调用的开销大,尤其是在循环内的时候,需要仔细考虑
  • move并不会一定真的能移动,它只是将左值强转成右值,只有当该用户自定义类型重载了移动构造和移动运算符重载函数时才会进行移动操作
  • 现代编译在处理返回值的时候,通常都会进行返回值优化,尤其是标准库的组件,使用move来接收返回值反而会增加开销
  • 移动之后的对象就被析构,所以通常是对一些临时对象,或者不再使用的对象进行移动操作。如果还要继续使用该对象,就要使用拷贝而不是移动操作
  • 右值引用变量本身是个左值,如果想要右值引用指向右值引用,需要使用move转成右值
  • const 左值引用也可以指向右值,但是无法进行修改

最后

为了方便其他设备和平台的小伙伴观看往期文章,链接奉上:

公众号搜索Let us Coding,或者扫描下方二维码,关注公众号,即可获取最新文章。

看完如果觉得有帮助,欢迎点赞、收藏关注

深入理解 C++ 右值引用和移动语义:全面解析的更多相关文章

  1. [转][c++11]我理解的右值引用、移动语义和完美转发

    c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...

  2. 【转】C++11 标准新特性: 右值引用与转移语义

    VS2013出来了,对于C++来说,最大的改变莫过于对于C++11新特性的支持,在网上搜了一下C++11的介绍,发现这篇文章非常不错,分享给大家同时自己作为存档. 原文地址:http://www.ib ...

  3. C++11 标准新特性: 右值引用与转移语义

    文章出处:https://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/ 新特性的目的 右值引用 (Rvalue Referene) ...

  4. C++11 右值引用和转移语义

    新特性的目的 右值引用 (Rvalue Referene) 是 C++ 新标准 (C++11, 11 代表 2011 年 ) 中引入的新特性 , 它实现了转移语义 (Move Sementics) 和 ...

  5. C++11 右值引用 与 转移语义

    新特性的目的 右值引用(R-value Reference)是C++新标准(C++11, 11代表2011年)中引入的新特性,它实现了转移语义(Move Semantics)和精确传递(Perfect ...

  6. [c++11]右值引用、移动语义和完美转发

    c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...

  7. 关于C++11右值引用和移动语义的探究

    关于C++11右值引用和移动语义的探究

  8. C++11新特性之右值引用(&&)、移动语义(move)、完美转换(forward)

    1. 右值引用 个人认为右值引用的目的主要是为了是减少内存拷贝,优化性能. 比如下面的代码: String Fun() { String str = "hello world"; ...

  9. C++右值引用与转移语义

    std::forwad? C++11 中定义的 T&& 的推导规则为: 右值实参为右值引用,左值实参仍然为左值引用. 参考: 右值引用与转移语义

  10. c++11的右值引用、移动语义

    对于c++11来说移动语义是一个重要的概念,一直以来我对这个概念都似懂非懂.最近翻翻资料感觉突然开窍,因此记下.其实搞懂之后就会发现这个概念很简单,并无什么高深的地方. 先说说右值引用.右值一般指的是 ...

随机推荐

  1. 一文详解云上自动化部署集群管理工具 Nebula Operator

    本文首发于 Nebula Graph 公众号:Nebula Operator 开源啦!一文详解这个云上自动化部署集群管理工具 在介绍 Nebula Operator 之前,让我们先来了解下什么是 Op ...

  2. [逆向] PE文件学习

    目录 概述 MS-DOS 头部 IMAGE_DOS_HEADER PE头 IMAGE_NT_HEADER IMAGE_FILE_HEADER IMAGE_OPTIONAL_HEADER32 数据目录列 ...

  3. MySQL之过滤条件

    [一]筛选过滤条件 [1]查询语句 -- 查询当前表中的指定字段的数据 select id,name from emp where id > 3; [2]创建数据表 create databas ...

  4. base64实现图片多图上传功能

    function webPic_upload($savepath,$url_data){$mark=ture; $pic_url=''; if(is_array($url_data)){ foreac ...

  5. 幕布 和 xmind 的大纲模式 都很不错

    幕布 和 xmind 的大纲模式 都很不错 xmind 思维导图 ctrl+a 折叠所有子分支 然后就能一级一级展开 最后选择用 xmind的大纲模式,因为免费. 幕布收费,限300个节点,小数据量的 ...

  6. centos 添加 公钥,root不用输入密码 ssh-keygen

    centos 添加 公钥,root不用输入密码 ssh-keygen -t rsa -C "yourEmail" 一通回车后,生成 C:\Users\Reciter/.ssh/id ...

  7. 在使用sudo apt-get -f install的时候,出现了更换介质的问题-依赖问题

    这四个选项都选上,然后apt-get update 在修补依赖问题,apt-get -f install      就好了

  8. [转载]Linux根据关键词查找文件/函数/结构体命令整理

    本文来自博客园,作者:Jcpeng_std,转载请注明原文链接:https://www.cnblogs.com/JCpeng/p/15077235.html 一.查找文件 使用 Linux 经常会遇到 ...

  9. 干货分享 | UE游戏鼠标双击判定

    UE虚幻引擎对于游戏开发者来说都不陌生,市面上有47%主机游戏使用虚幻引擎开发游戏.作为是一款游戏的核心动力,它的功能十分完善,囊括了场景制作.灯光渲染.动作镜头.粒子特效.材质蓝图等.本文介绍了虚幻 ...

  10. 03.视频播放器Api说明

    03.视频播放器Api说明 目录介绍 01.最简单的播放 02.如何切换视频内核 03.切换视频模式 04.切换视频清晰度 05.视频播放监听 06.列表中播放处理 07.悬浮窗口播放 08.其他重要 ...