左值和右值的定义

在C++中,可以放到赋值操作符=左边的是左值,可以放到赋值操作符右边的是右值。有些变量既可以当左值又可以当右值。进一步来讲,左值为Lvalue,其实L代表Location,表示在内存中可以寻址,可以给它赋值(常量const类型也可以寻址,但是不能赋值),Rvalue中的R代表Read,就是可以知道它的值。例如:
int a=3;
a在内存中有地址,而3没有,但是可以read到它的值。
3=4;
这个是错误的,因为3的内存中没有地址,不能当作左值。
下面这个语句不容易出错
a++=3;
这个语句编译通不过的,原因在于a++是先使用a的值,再给a加1。实现如下:
  1. {
  2. int tmp=a;
  3. a=a+;
  4. return tmp;
  5. }

++a是右值。

++a=3;
这个是正确的,++a的实现如下:
  1. {
  2. a=a+;
  3. return &a;
  4. }

显然++a的效率高。

左值符号&和右值符号&&

左值的声明符号为&,右值的声明符号为&&。在C++中,临时对象不能作为左值,但是可以作为常量引用const &
  1. #include<iostream>
  2. void print_lvalue(int& i)//左值
  3. {
  4. std::cout << "Lvalue:" << i << std::endl;
  5. }
  6. void print_rvalue(int&& i)//右值
  7. {
  8. std::cout << "Rvalue:" << i << std::endl;
  9. }
  10.  
  11. int main()
  12. {
  13. int i = ;
  14. print_lvalue(i);
  15. print_rvalue();
  16. //print_lvalue(1)会出错
  17. //print_lvalue(const int& i)可以使用print_lvalue(1)
  18. return ;
  19. }

C++11中的move

有时候我们希望把左值当作右值来使用,例如一个变量的值,不再使用了,希望把它的值转移出去,C++11中的std::move就为我们提供了将左值引用转为右值引用的方法。
  1. #include<iostream>
  2. void print_value(int& i)//左值
  3. {
  4. std::cout << "Lvalue:" << i << std::endl;
  5. }
  6. void print_value(int&& i)//右值
  7. {
  8. std::cout << "Rvalue:" << i << std::endl;
  9. }
  10. int main()
  11. {
  12. int i = ;
  13. print_value(i);
  14. print_value(std::move(i));
  15. return ;
  16. }

最长用的交换函数

  1. void swap(T& a, T& b)
  2. {
  3. T tmp = std::move(a);
  4. a = std::move(b);
  5. b = std::move(tmp);
  6. }

避免了3次拷贝。

精确值传递

std::forward主要用于模板编程中,值传递的问题。可以推测参数是左值引用还是右值引用,精确传递值。
 
  1. std::movestd::forwardC++0x中新增的标准库函数,分别用于实现移动语义和完美转发。
  2. 下面让我们分析一下这两个函数在gcc4.6中的具体实现。
  3.  
  4. 预备知识
  5. .引用折叠规则:
  6. X& + & => X&
  7. X&& + & => X&
  8. X& + && => X&
  9. X&& + && => X&&
  10.  
  11. .函数模板参数推导规则(右值引用参数部分):
  12. 当函数模板的模板参数为T而函数形参为T&&(右值引用)时适用本规则。
  13. 若实参为左值 U& ,则模板参数 T 应推导为引用类型 U&
  14. (根据引用折叠规则, U& + && => U&, T&& U&,故T U&
  15. 若实参为右值 U&& ,则模板参数 T 应推导为非引用类型 U
  16. (根据引用折叠规则, UU&& + && => U&&, T&& U&&,故T UU&&,这里强制规定T U
  17.  
  18. .std::remove_referenceC++0x标准库中的元函数,其功能为去除类型中的引用。
  19. std::remove_reference<U&>::type U
  20. std::remove_reference<U&&>::type U
  21. std::remove_reference<U>::type U
  22.  
  23. .以下语法形式将把表达式 t 转换为T类型的右值(准确的说是无名右值引用,是右值的一种)
  24. static_cast<T&&>(t)
  25. .无名的右值引用是右值
  26. 具名的右值引用是左值。
  27. .注:本文中 含义为“即,等价于“。
  28.  
  29. std::move
  30.  
  31. 函数功能
  32.  
  33. std::move(t) 负责将表达式 t 转换为右值,使用这一转换意味着你不再关心 t 的内容,它可以通过被移动(窃取)来解决移动语意问题。
  34.  
  35. 源码与测试代码
  36.  
  37. [cpp] view plain copy print?
  38. .template<typename _Tp>
  39. . inline typename std::remove_reference<_Tp>::type&&
  40. . move(_Tp&& __t)
  41. . { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
  42.  
  43. [cpp] view plain copy print?
  44. .#include<iostream>
  45. .using namespace std;
  46. .
  47. .struct X {};
  48. .
  49. .int main()
  50. .{
  51. . X a;
  52. . X&& b = move(a);
  53. . X&& c = move(X());
  54. .}
  55.  
  56. 代码说明
  57. .测试代码第9行用X类型的左值 a 来测试move函数,根据标准X类型的右值引用 b 只能绑定X类型的右值,所以 move(a) 的返回值必然是X类型的右值。
  58. .测试代码第10行用X类型的右值 X() 来测试move函数,根据标准X类型的右值引用 c 只能绑定X类型的右值,所以 move(X()) 的返回值必然是X类型的右值。
  59. .首先我们来分析 move(a) 这种用左值参数来调用move函数的情况。
  60. .模拟单步调用来到源码第3行,_Tp&& X&, __t a
  61.  
  62. .根据函数模板参数推导规则,_Tp&& X& 可推出 _Tp X&
  63.  
  64. .typename std::remove_reference<_Tp>::type X
  65. typename std::remove_reference<_Tp>::type&& X&&
  66. .再次单步调用进入move函数实体所在的源码第4行。
  67. .static_cast<typename std::remove_reference<_Tp>::type&&>(__t) static_cast<X&&>(a)
  68. .根据标准 static_cast<X&&>(a) 将把左值 a 转换为X类型的无名右值引用。
  69. .然后我们再来分析 move(X()) 这种用右值参数来调用move函数的情况。
  70. .模拟单步调用来到源码第3行,_Tp&& X&&, __t X()
  71. .根据函数模板参数推导规则,_Tp&& X&& 可推出 _Tp X
  72.  
  73. .typename std::remove_reference<_Tp>::type X
  74. typename std::remove_reference<_Tp>::type&& X&&
  75. .再次单步调用进入move函数实体所在的源码第4行。
  76. .static_cast<typename std::remove_reference<_Tp>::type&&>(__t) static_cast<X&&>(X())
  77. .根据标准 static_cast<X&&>(X()) 将把右值 X() 转换为X类型的无名右值引用。
  78. .由916可知源码中std::move函数的具体实现符合标准,
  79. 因为无论用左值a还是右值X()做参数来调用std::move函数,
  80. 该实现都将返回无名的右值引用(右值的一种),符合标准中该函数的定义。
  81.  
  82. std::forward
  83.  
  84. 函数功能
  85.  
  86. std::forward<T>(u) 有两个参数:T u。当T为左值引用类型时,u将被转换为T类型的左值,否则u将被转换为T类型右值。如此定义std::forward是为了在使用右值引用参数的函数模板中解决参数的完美转发问题。
  87.  
  88. 源码与测试代码
  89.  
  90. [cpp] view plain copy print?
  91. ./// forward (as per N3143)
  92. .template<typename _Tp>
  93. . inline _Tp&&
  94. . forward(typename std::remove_reference<_Tp>::type& __t)
  95. . { return static_cast<_Tp&&>(__t); }
  96. .
  97. .template<typename _Tp>
  98. . inline _Tp&&
  99. . forward(typename std::remove_reference<_Tp>::type&& __t)
  100. . {
  101. . static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
  102. . " substituting _Tp is an lvalue reference type");
  103. . return static_cast<_Tp&&>(__t);
  104. . }
  105.  
  106. [cpp] view plain copy print?
  107. .#include<iostream>
  108. .using namespace std;
  109. .
  110. .struct X {};
  111. .void inner(const X&) {cout << "inner(const X&)" << endl;}
  112. .void inner(X&&) {cout << "inner(X&&)" << endl;}
  113. .template<typename T>
  114. .void outer(T&& t) {inner(forward<T>(t));}
  115. .
  116. .int main()
  117. .{
  118. . X a;
  119. . outer(a);
  120. . outer(X());
  121. . inner(forward<X>(X()));
  122. .}
  123. .//inner(const X&)
  124. .//inner(X&&)
  125. .//inner(X&&)
  126.  
  127. 代码说明
  128. .测试代码第13行用X类型的左值 a 来测试forward函数,程序输出表明 outer(a) 调用的是 inner(const X&) 版本,从而证明函数模板outer调用forward函数在将参数左值 a 转发给了inner函数时,成功地保留了参数 a 的左值属性。
  129. .测试代码第14行用X类型的右值 X() 来测试forward函数,程序输出表明 outer(X()) 调用的是 inner(X&&) 版本,从而证明函数模板outer调用forward函数在将参数右值 X() 转发给了inner函数时,成功地保留了参数 X() 的右值属性。
  130. .首先我们来分析 outer(a) 这种调用forward函数转发左值参数的情况。
  131. .模拟单步调用来到测试代码第8行,T&& X&, t a
  132.  
  133. .根据函数模板参数推导规则,T&& X& 可推出 T X&
  134.  
  135. .forward<T>(t) forward<X&>(t),其中 t 为指向 a 的左值引用。
  136.  
  137. .再次单步调用进入forward函数实体所在的源码第4行或第9行。
  138.  
  139. .先尝试匹配源码第4行的forward函数,_Tp X&
  140. .typename std::remove_reference<_Tp>::type X
  141. typename std::remove_reference<_Tp>::type& X&
  142. .形参 __t 与实参 t 类型相同,因此函数匹配成功。
  143. .再尝试匹配源码第9行的forward函数,_Tp X&
  144. .typename std::remove_reference<_Tp>::type X
  145. typename std::remove_reference<_Tp>::type&& X&&
  146. .形参 __t 与实参 t 类型不同,因此函数匹配失败。
  147. .由1013可知7单步调用实际进入的是源码第4行的forward函数。
  148. .static_cast<_Tp&&>(__t) static_cast<X&>(t) a
  149. .inner(forward<T>(t)) inner(static_cast<X&>(t)) inner(a)
  150. .outer(a) inner(forward<T>(t)) inner(a)
  151. 再次单步调用将进入测试代码第5行的inner(const X&) 版本,左值参数转发成功。
  152. .然后我们来分析 outer(X()) 这种调用forward函数转发右值参数的情况。
  153. .模拟单步调用来到测试代码第8行,T&& X&&, t X()
  154. .根据函数模板参数推导规则,T&& X&& 可推出 T X
  155.  
  156. .forward<T>(t) forward<X>(t),其中 t 为指向 X() 的右值引用。
  157.  
  158. .再次单步调用进入forward函数实体所在的源码第4行或第9行。
  159. .先尝试匹配源码第4行的forward函数,_Tp X
  160. .typename std::remove_reference<_Tp>::type X
  161. typename std::remove_reference<_Tp>::type& X&
  162. .形参 __t 与实参 t 类型相同,因此函数匹配成功。
  163. .再尝试匹配源码第9行的forward函数,_Tp X
  164. .typename std::remove_reference<_Tp>::type X
  165. typename std::remove_reference<_Tp>::type&& X&&
  166. .形参 __t 与实参 t 类型不同,因此函数匹配失败。
  167. .由2528可知22单步调用实际进入的仍然是源码第4行的forward函数。
  168.  
  169. .static_cast<_Tp&&>(__t) static_cast<X&&>(t) X()。
  170. .inner(forward<T>(t)) inner(static_cast<X&&>(t)) inner(X())。
  171. .outer(X()) inner(forward<T>(t)) inner(X())
  172. 再次单步调用将进入测试代码第6行的inner(X&&) 版本,右值参数转发成功。
  173.  
  174. .由1732可知源码中std::forward函数的具体实现符合标准,
  175. 因为无论用左值a还是右值X()做参数来调用带有右值引用参数的函数模板outer
  176. 只要在outer函数内使用std::forward函数转发参数,
  177. 就能保留参数的左右值属性,从而实现了函数模板参数的完美转发。

c++11の的左值、右值以及move,foward的更多相关文章

  1. C++11之右值引用(一):从左值右值到右值引用

    C++98中规定了左值和右值的概念,但是一般程序员不需要理解的过于深入,因为对于C++98,左值和右值的划分一般用处不大,但是到了C++11,它的重要性开始显现出来. C++98标准明确规定: 左值是 ...

  2. C++ 左值 右值

    最近在研究C++ 左值 右值,搬运.收集了一些别人的资料,供自己记录和学习,若以后看到了更好的解释,会继续补充.(打“?”是我自己不明白的地方 )   参考:<Boost程序库探秘——深度解析C ...

  3. C++ 左值与右值 右值引用 引用折叠 => 完美转发

    左值与右值 什么是左值?什么是右值? 在C++里没有明确定义.看了几个版本,有名字的是左值,没名字的是右值.能被&取地址的是左值,不能被&取地址的是右值.而且左值与右值可以发生转换. ...

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

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

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

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

  6. 透彻理解C++11新特性:右值引用、std::move、std::forward

    目录 浅拷贝.深拷贝 左值.右值 右值引用类型 强转右值 std::move 重新审视右值引用 右值引用类型和右值的关系 函数参数传递 函数返还值传递 万能引用 引用折叠 完美转发 std::forw ...

  7. c++ 左值右值 函数模板

    1.先看一段代码,这就是一种函数模板的用法,但是红色的部分如果把a写成a++或者写成一个常量比如1,都是编译不过的,因为如果是a++的话,实际上首先是取得a的 值0,而0作为一个常量没有地址.写成1也 ...

  8. 左值&右值

    一.引子 我们所谓的左值.右值,正确的说法应该是左值表达式.右值表达式. 因为C++的表达式不是左值就是右值. 在C中,左值指的是既能够出现在等号左边也能出现在等号右边的表达式,右值指的则是只能出现在 ...

  9. C语言几个术语: 数据对象,左值,右值

    1. 数据对象 赋值表达式语句的目的是把值存储到内存位置上. 用于存储值的数据存储区域统称为数据对象. 2. 左值 左值是C语言的术语, 用于标识特定数据对象的名称或表达式. 对象指的是实际的数据存储 ...

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

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

随机推荐

  1. Hibernate学习(四)———— 双向多对多映射关系

    一.小疑问的解答 问题一:到这里,有很多学习者会感到困惑,因为他不知道使用hibernate是不是需要自己去创建表,还是hibernate全自动,如果需要自己创建表,那么主外键这种设置也是自己设置吗? ...

  2. a 标签提交表单

    document.getElementById('ECS_FORMBUY').submit();

  3. Linux ELF 文件格式

    ELF 文件类型 ELF (Executable Linkable Format) 是 linux 下的可执行文件格式,与 windows 下的 PE (Portable Executable) 格式 ...

  4. 百度api查询多个地址的经纬度的问题

    在使用百度api查询多个地址的经纬度的时候,由于百度api提供的经纬度查询方法是回调函数,并且后续操作必须等经纬度获取完成才能进行,问题就存在于怎么判断所有地点是否都回调完成了,问了之前的一个前端大佬 ...

  5. 自定义滚动条mCustomScrollbar

    mCustomScrollbar 是个基于 jQuery UI 的自定义滚动条插件,它可以让你灵活的通过 CSS 定义网页的滚动条,并且垂直和水平两个方向的滚动条都可以定义,它通过 Brandon A ...

  6. Java容器类源码分析之Iterator与ListIterator迭代器(基于JDK8)

    一.基本概念 迭代器是一个对象,也是一种设计模式,Java有两个用来实实现迭代器的接口,分别是Iterator接口和继承自Iterator的ListIterator接口.实现迭代器接口的类的对象有遍历 ...

  7. 【Java基础】9、Enumeration接口和Iterator接口的区别

    package java.util; public interface Enumeration<E> {     boolean hasMoreElements();     E next ...

  8. Pseudocode MD5 CODE

    //Note: All variables are unsigned 32 bit and wrap modulo 2^32 when calculating var int[64] s, K //s ...

  9. blfs(systemv版本)学习笔记-使用apache创建简单的网页服务器

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! apache项目地址:http://www.linuxfromscratch.org/blfs/view/stable/serv ...

  10. 微信小程序下拉框之二维数组或对象

    在项目中,我们大多数时候传的值并不是需要这个下标,而是其他的值.像我项目中,需要获取到的是它对应的Id,那么我们如何通过它的这个下标值返回你想要的值呢? 通过picker返回的索引值,去获取匹配你想获 ...