c++11の的左值、右值以及move,foward
左值和右值的定义
{
int tmp=a;
a=a+;
return tmp;
}
++a是右值。
{
a=a+;
return &a;
}
显然++a的效率高。
左值符号&和右值符号&&
#include<iostream>
void print_lvalue(int& i)//左值
{
std::cout << "Lvalue:" << i << std::endl;
}
void print_rvalue(int&& i)//右值
{
std::cout << "Rvalue:" << i << std::endl;
} int main()
{
int i = ;
print_lvalue(i);
print_rvalue();
//print_lvalue(1)会出错
//print_lvalue(const int& i)可以使用print_lvalue(1)
return ;
}
C++11中的move
#include<iostream>
void print_value(int& i)//左值
{
std::cout << "Lvalue:" << i << std::endl;
}
void print_value(int&& i)//右值
{
std::cout << "Rvalue:" << i << std::endl;
}
int main()
{
int i = ;
print_value(i);
print_value(std::move(i));
return ;
}
最长用的交换函数
void swap(T& a, T& b)
{
T tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
}
避免了3次拷贝。
精确值传递
std::move和std::forward是C++0x中新增的标准库函数,分别用于实现移动语义和完美转发。
下面让我们分析一下这两个函数在gcc4.6中的具体实现。 预备知识
.引用折叠规则:
X& + & => X&
X&& + & => X&
X& + && => X&
X&& + && => X&& .函数模板参数推导规则(右值引用参数部分):
当函数模板的模板参数为T而函数形参为T&&(右值引用)时适用本规则。
若实参为左值 U& ,则模板参数 T 应推导为引用类型 U& 。
(根据引用折叠规则, U& + && => U&, 而T&& ≡ U&,故T ≡ U& )
若实参为右值 U&& ,则模板参数 T 应推导为非引用类型 U 。
(根据引用折叠规则, U或U&& + && => U&&, 而T&& ≡ U&&,故T ≡ U或U&&,这里强制规定T ≡ U ) .std::remove_reference为C++0x标准库中的元函数,其功能为去除类型中的引用。
std::remove_reference<U&>::type ≡ U
std::remove_reference<U&&>::type ≡ U
std::remove_reference<U>::type ≡ U .以下语法形式将把表达式 t 转换为T类型的右值(准确的说是无名右值引用,是右值的一种)
static_cast<T&&>(t)
.无名的右值引用是右值
具名的右值引用是左值。
.注:本文中 ≡ 含义为“即,等价于“。 std::move 函数功能 std::move(t) 负责将表达式 t 转换为右值,使用这一转换意味着你不再关心 t 的内容,它可以通过被移动(窃取)来解决移动语意问题。 源码与测试代码 [cpp] view plain copy print?
.template<typename _Tp>
. inline typename std::remove_reference<_Tp>::type&&
. move(_Tp&& __t)
. { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); } [cpp] view plain copy print?
.#include<iostream>
.using namespace std;
.
.struct X {};
.
.int main()
.{
. X a;
. X&& b = move(a);
. X&& c = move(X());
.} 代码说明
.测试代码第9行用X类型的左值 a 来测试move函数,根据标准X类型的右值引用 b 只能绑定X类型的右值,所以 move(a) 的返回值必然是X类型的右值。
.测试代码第10行用X类型的右值 X() 来测试move函数,根据标准X类型的右值引用 c 只能绑定X类型的右值,所以 move(X()) 的返回值必然是X类型的右值。
.首先我们来分析 move(a) 这种用左值参数来调用move函数的情况。
.模拟单步调用来到源码第3行,_Tp&& ≡ X&, __t ≡ a 。 .根据函数模板参数推导规则,_Tp&& ≡ X& 可推出 _Tp ≡ X& 。 .typename std::remove_reference<_Tp>::type ≡ X 。
typename std::remove_reference<_Tp>::type&& ≡ X&& 。
.再次单步调用进入move函数实体所在的源码第4行。
.static_cast<typename std::remove_reference<_Tp>::type&&>(__t) ≡ static_cast<X&&>(a)
.根据标准 static_cast<X&&>(a) 将把左值 a 转换为X类型的无名右值引用。
.然后我们再来分析 move(X()) 这种用右值参数来调用move函数的情况。
.模拟单步调用来到源码第3行,_Tp&& ≡ X&&, __t ≡ X() 。
.根据函数模板参数推导规则,_Tp&& ≡ X&& 可推出 _Tp ≡ X 。 .typename std::remove_reference<_Tp>::type ≡ X 。
typename std::remove_reference<_Tp>::type&& ≡ X&& 。
.再次单步调用进入move函数实体所在的源码第4行。
.static_cast<typename std::remove_reference<_Tp>::type&&>(__t) ≡ static_cast<X&&>(X())
.根据标准 static_cast<X&&>(X()) 将把右值 X() 转换为X类型的无名右值引用。
.由9和16可知源码中std::move函数的具体实现符合标准,
因为无论用左值a还是右值X()做参数来调用std::move函数,
该实现都将返回无名的右值引用(右值的一种),符合标准中该函数的定义。 std::forward 函数功能 std::forward<T>(u) 有两个参数:T 与 u。当T为左值引用类型时,u将被转换为T类型的左值,否则u将被转换为T类型右值。如此定义std::forward是为了在使用右值引用参数的函数模板中解决参数的完美转发问题。 源码与测试代码 [cpp] view plain copy print?
./// forward (as per N3143)
.template<typename _Tp>
. inline _Tp&&
. forward(typename std::remove_reference<_Tp>::type& __t)
. { return static_cast<_Tp&&>(__t); }
.
.template<typename _Tp>
. inline _Tp&&
. forward(typename std::remove_reference<_Tp>::type&& __t)
. {
. static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
. " substituting _Tp is an lvalue reference type");
. return static_cast<_Tp&&>(__t);
. } [cpp] view plain copy print?
.#include<iostream>
.using namespace std;
.
.struct X {};
.void inner(const X&) {cout << "inner(const X&)" << endl;}
.void inner(X&&) {cout << "inner(X&&)" << endl;}
.template<typename T>
.void outer(T&& t) {inner(forward<T>(t));}
.
.int main()
.{
. X a;
. outer(a);
. outer(X());
. inner(forward<X>(X()));
.}
.//inner(const X&)
.//inner(X&&)
.//inner(X&&) 代码说明
.测试代码第13行用X类型的左值 a 来测试forward函数,程序输出表明 outer(a) 调用的是 inner(const X&) 版本,从而证明函数模板outer调用forward函数在将参数左值 a 转发给了inner函数时,成功地保留了参数 a 的左值属性。
.测试代码第14行用X类型的右值 X() 来测试forward函数,程序输出表明 outer(X()) 调用的是 inner(X&&) 版本,从而证明函数模板outer调用forward函数在将参数右值 X() 转发给了inner函数时,成功地保留了参数 X() 的右值属性。
.首先我们来分析 outer(a) 这种调用forward函数转发左值参数的情况。
.模拟单步调用来到测试代码第8行,T&& ≡ X&, t ≡ a 。 .根据函数模板参数推导规则,T&& ≡ X& 可推出 T ≡ X& 。 .forward<T>(t) ≡ forward<X&>(t),其中 t 为指向 a 的左值引用。 .再次单步调用进入forward函数实体所在的源码第4行或第9行。 .先尝试匹配源码第4行的forward函数,_Tp ≡ X& 。
.typename std::remove_reference<_Tp>::type ≡ X 。
typename std::remove_reference<_Tp>::type& ≡ X& 。
.形参 __t 与实参 t 类型相同,因此函数匹配成功。
.再尝试匹配源码第9行的forward函数,_Tp ≡ X& 。
.typename std::remove_reference<_Tp>::type ≡ X 。
typename std::remove_reference<_Tp>::type&& ≡ X&& 。
.形参 __t 与实参 t 类型不同,因此函数匹配失败。
.由10与13可知7单步调用实际进入的是源码第4行的forward函数。
.static_cast<_Tp&&>(__t) ≡ static_cast<X&>(t) ≡ a。
.inner(forward<T>(t)) ≡ inner(static_cast<X&>(t)) ≡ inner(a) 。
.outer(a) ≡ inner(forward<T>(t)) ≡ inner(a)
再次单步调用将进入测试代码第5行的inner(const X&) 版本,左值参数转发成功。
.然后我们来分析 outer(X()) 这种调用forward函数转发右值参数的情况。
.模拟单步调用来到测试代码第8行,T&& ≡ X&&, t ≡ X() 。
.根据函数模板参数推导规则,T&& ≡ X&& 可推出 T ≡ X 。 .forward<T>(t) ≡ forward<X>(t),其中 t 为指向 X() 的右值引用。 .再次单步调用进入forward函数实体所在的源码第4行或第9行。
.先尝试匹配源码第4行的forward函数,_Tp ≡ X 。
.typename std::remove_reference<_Tp>::type ≡ X 。
typename std::remove_reference<_Tp>::type& ≡ X& 。
.形参 __t 与实参 t 类型相同,因此函数匹配成功。
.再尝试匹配源码第9行的forward函数,_Tp ≡ X 。
.typename std::remove_reference<_Tp>::type ≡ X 。
typename std::remove_reference<_Tp>::type&& ≡ X&& 。
.形参 __t 与实参 t 类型不同,因此函数匹配失败。
.由25与28可知22单步调用实际进入的仍然是源码第4行的forward函数。 .static_cast<_Tp&&>(__t) ≡ static_cast<X&&>(t) ≡ X()。
.inner(forward<T>(t)) ≡ inner(static_cast<X&&>(t)) ≡ inner(X())。
.outer(X()) ≡ inner(forward<T>(t)) ≡ inner(X())
再次单步调用将进入测试代码第6行的inner(X&&) 版本,右值参数转发成功。 .由17和32可知源码中std::forward函数的具体实现符合标准,
因为无论用左值a还是右值X()做参数来调用带有右值引用参数的函数模板outer,
只要在outer函数内使用std::forward函数转发参数,
就能保留参数的左右值属性,从而实现了函数模板参数的完美转发。
c++11の的左值、右值以及move,foward的更多相关文章
- C++11之右值引用(一):从左值右值到右值引用
C++98中规定了左值和右值的概念,但是一般程序员不需要理解的过于深入,因为对于C++98,左值和右值的划分一般用处不大,但是到了C++11,它的重要性开始显现出来. C++98标准明确规定: 左值是 ...
- C++ 左值 右值
最近在研究C++ 左值 右值,搬运.收集了一些别人的资料,供自己记录和学习,若以后看到了更好的解释,会继续补充.(打“?”是我自己不明白的地方 ) 参考:<Boost程序库探秘——深度解析C ...
- C++ 左值与右值 右值引用 引用折叠 => 完美转发
左值与右值 什么是左值?什么是右值? 在C++里没有明确定义.看了几个版本,有名字的是左值,没名字的是右值.能被&取地址的是左值,不能被&取地址的是右值.而且左值与右值可以发生转换. ...
- C++11新特性之右值引用(&&)、移动语义(move)、完美转换(forward)
1. 右值引用 个人认为右值引用的目的主要是为了是减少内存拷贝,优化性能. 比如下面的代码: String Fun() { String str = "hello world"; ...
- [转][c++11]我理解的右值引用、移动语义和完美转发
c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...
- 透彻理解C++11新特性:右值引用、std::move、std::forward
目录 浅拷贝.深拷贝 左值.右值 右值引用类型 强转右值 std::move 重新审视右值引用 右值引用类型和右值的关系 函数参数传递 函数返还值传递 万能引用 引用折叠 完美转发 std::forw ...
- c++ 左值右值 函数模板
1.先看一段代码,这就是一种函数模板的用法,但是红色的部分如果把a写成a++或者写成一个常量比如1,都是编译不过的,因为如果是a++的话,实际上首先是取得a的 值0,而0作为一个常量没有地址.写成1也 ...
- 左值&右值
一.引子 我们所谓的左值.右值,正确的说法应该是左值表达式.右值表达式. 因为C++的表达式不是左值就是右值. 在C中,左值指的是既能够出现在等号左边也能出现在等号右边的表达式,右值指的则是只能出现在 ...
- C语言几个术语: 数据对象,左值,右值
1. 数据对象 赋值表达式语句的目的是把值存储到内存位置上. 用于存储值的数据存储区域统称为数据对象. 2. 左值 左值是C语言的术语, 用于标识特定数据对象的名称或表达式. 对象指的是实际的数据存储 ...
- 【转】C++11 标准新特性: 右值引用与转移语义
VS2013出来了,对于C++来说,最大的改变莫过于对于C++11新特性的支持,在网上搜了一下C++11的介绍,发现这篇文章非常不错,分享给大家同时自己作为存档. 原文地址:http://www.ib ...
随机推荐
- lightswitch Grid 控件添加 CheckBox 多选
ACTIVATING MULTI SELECTION WITH CHECKBOXES IN A LIGHTSWITCH GRID WITH A ONE-LINER 2013/04/02 · by pa ...
- Nginx反代Mogilefs分布式储存示例
一.分布式存储系统简介 随着信息技术不断的发展,给我们带来便利的同时,不断增加的数据量级.信息之间的连接关联越来越复杂.数据访问的并发量日益增加对I/O的要求越来越高.数据类型越来越复杂等难题也成为信 ...
- ajax与文件上传
一.ajax ajax(Asynchronous JavaScript And XML):异步JavaScript和XML,即使用JavaScript语句与服务器进行异步交互,传输的数据为XML(也可 ...
- C#实现加简单的Http请求
通过.Net中的两个类 HttpWebRequest 类, HttpWebResponse 类来实现Http的请求,响应处理. 第一个小测试是请求百度首页( http://www.baidu.com ...
- openWin和openFrame 设置透明背景
openWin简单点说就是:像是一个浏览器 openFrame就是对应openWin浏览器里面打开的每一个网页 有些操作只能在openWin里面执行,比如监听安卓返回事件,只能在openWin里面才有 ...
- DotNetCore学习-3.管道中间件
中间件是用于组成应用程序管道来处理请求和响应的组件.管道内的每个组件都可以选择是否将请求交给下一个组件,并在管道中调用下一个组件之前和之后执行一些操作. 请求委托被用来建立请求管道,并处理每一个HTT ...
- webpack4 系列教程(九): CSS Tree Shaking
教程所示图片使用的是 github 仓库图片,网速过慢的朋友请移步原文地址 有空就来看看个人技术小站, 我一直都在 0. 课程介绍和资料 本次课程的代码目录(如下图所示): >>> ...
- Django Rest framework 之 版本
RESTful 规范 django rest framework 之 认证(一) django rest framework 之 权限(二) django rest framework 之 节流(三) ...
- loadsh常用函数
此篇文章会记录常用的lodash函数 防抖函数:_.debounce() 创建一个去缓冲函数,该函数将自上次调用函数以来经过设置的等待毫秒后调用func. 去缓冲函数带有一个取消方法来取消延迟的fun ...
- 【读书笔记】iOS-解析XML
使用最广泛的解析XML文档的方法有两种,一种基于SAX,另一种基于DOM.SAX解析器是事件驱动型的,在解析时增量地读取XML文档,当解析器识别出一个结点的时候会调用相应的委托方法. 参考资料< ...