深入理解 C++ 右值引用和移动语义:全面解析
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
之前,如果没有手动声明,编译器会给一个用于自定义类型(包括class
和struct
)自动生成的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
的大部分组件都支持移动语义,比如vector
,string
等即可以通过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++ 右值引用和移动语义:全面解析的更多相关文章
- [转][c++11]我理解的右值引用、移动语义和完美转发
c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...
- 【转】C++11 标准新特性: 右值引用与转移语义
VS2013出来了,对于C++来说,最大的改变莫过于对于C++11新特性的支持,在网上搜了一下C++11的介绍,发现这篇文章非常不错,分享给大家同时自己作为存档. 原文地址:http://www.ib ...
- C++11 标准新特性: 右值引用与转移语义
文章出处:https://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/ 新特性的目的 右值引用 (Rvalue Referene) ...
- C++11 右值引用和转移语义
新特性的目的 右值引用 (Rvalue Referene) 是 C++ 新标准 (C++11, 11 代表 2011 年 ) 中引入的新特性 , 它实现了转移语义 (Move Sementics) 和 ...
- C++11 右值引用 与 转移语义
新特性的目的 右值引用(R-value Reference)是C++新标准(C++11, 11代表2011年)中引入的新特性,它实现了转移语义(Move Semantics)和精确传递(Perfect ...
- [c++11]右值引用、移动语义和完美转发
c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...
- 关于C++11右值引用和移动语义的探究
关于C++11右值引用和移动语义的探究
- C++11新特性之右值引用(&&)、移动语义(move)、完美转换(forward)
1. 右值引用 个人认为右值引用的目的主要是为了是减少内存拷贝,优化性能. 比如下面的代码: String Fun() { String str = "hello world"; ...
- C++右值引用与转移语义
std::forwad? C++11 中定义的 T&& 的推导规则为: 右值实参为右值引用,左值实参仍然为左值引用. 参考: 右值引用与转移语义
- c++11的右值引用、移动语义
对于c++11来说移动语义是一个重要的概念,一直以来我对这个概念都似懂非懂.最近翻翻资料感觉突然开窍,因此记下.其实搞懂之后就会发现这个概念很简单,并无什么高深的地方. 先说说右值引用.右值一般指的是 ...
随机推荐
- JVM-对象实例化
JVM-对象实例化 1.创建对象的方式 new:最常见的方式.Xxx的静态方法,XxxBuilder/XxxFactory的静态方法 Class的newInstance方法:反射的方式,只能调用空参的 ...
- 【Java复健指南07】OOP中级02-重写与多态思想
前情提要:https://www.cnblogs.com/DAYceng/category/2227185.html 重写 注意事项和使用细节 方法重写也叫方法覆法,需要满足下面的条件 1.子类的方法 ...
- 【Azure 应用服务】Web App Service 中的 应用程序配置(Application Setting) 怎么获取key vault中的值
问题描述 App Service中,如何通过 Application Setting 来配置 Key Vault中的值呢? 问题解答 首先,App Service服务可以直接通过引用的方式,无需代码的 ...
- STM32 | STM32到底是什么?(第一天)
零基础 STM32 第一天 一.认知STM32 1.STM32概念 STM32:意法半导体基于ARM公司的Cortex-M内核开发的32位的高性能.低功耗单片机. ST:意法半导体 M:基于ARM公司 ...
- 第12章_MySQL数据类型
目录: https://www.cnblogs.com/xjwhaha/p/15844178.html 1. MySQL中的数据类型 类型 类型举例 整数类型 TINYINT.SMALLINT.MED ...
- 在winform中如何嵌入第三方软件窗体✨
相关win32api的学习 SetParent [DllImport("user32.dll ", EntryPoint = "SetParent")] pri ...
- Zabbix“专家坐诊”第198期问答汇总
问题一 Q:请问一下,自带的思科SNMP交换机模板,怎么不监控down的接口? A1:这种一般在自动发现规则里加个过滤器,过滤出IFSTATUS匹配(1|3)的就能实现只发现up的端口了. A2: 1 ...
- WPF之资源
目录 WPF对象资源的定义和查找 动态.静态使用资源 向程序添加二进制资源 字符串资源 非字符串资源 使用Pack URI路径访问二进制资源 WPF不但支持程序级的传统资源,同时还推出了独具特色的对象 ...
- Android WebView获取html源码
通过执行js语句来获取 val code = """ document.documentElement.outerHTML """.trim ...
- day26--Java集合09
Java集合09 18.TreeSet 元素无序:插入顺序和输出顺序不一致 可以按照一定的规则进行排序,具体排序方式取决于构造方法: TreeSet () :根据其元素的自然排序进行排序 TreeSe ...