c++11の的左值、右值以及move,foward

左值和右值的定义

在C++中,可以放到赋值操作符=左边的是左值,可以放到赋值操作符右边的是右值。有些变量既可以当左值又可以当右值。进一步来讲,左值为Lvalue,其实L代表Location,表示在内存中可以寻址,可以给它赋值(常量const类型也可以寻址,但是不能赋值),Rvalue中的R代表Read,就是可以知道它的值。例如:

int a=3;

a在内存中有地址,而3没有,但是可以read到它的值。

3=4;

这个是错误的,因为3的内存中没有地址,不能当作左值。

下面这个语句不容易出错

a++=3;

这个语句编译通不过的,原因在于a++是先使用a的值,再给a加1。实现如下:

{
    int tmp=a;
   a=a+1;
    return tmp;
}  

++a是右值。

++a=3;

这个是正确的,++a的实现如下:

{
   a=a+1;
   return &a;
}  

显然++a的效率高。

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

左值的声明符号为&,右值的声明符号为&&。在C++中,临时对象不能作为左值,但是可以作为常量引用const &

#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 = 0;
    print_lvalue(i);
    print_rvalue(1);
    //print_lvalue(1)会出错
    //print_lvalue(const int& i)可以使用print_lvalue(1)
   return 0;
}  

C++11中的move

有时候我们希望把左值当作右值来使用,例如一个变量的值,不再使用了,希望把它的值转移出去,C++11中的std::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 = 10;
    print_value(i);
    print_value(std::move(i));
    return 0;
}  

最长用的交换函数

void swap(T& a, T& b)
{
    T tmp = std::move(a);
    a = std::move(b);
    b = std::move(tmp);
}  

避免了3次拷贝。

精确值传递

std::forward主要用于模板编程中,值传递的问题。可以推测参数是左值引用还是右值引用,精确传递值。

std::move和std::forward是C++0x中新增的标准库函数,分别用于实现移动语义和完美转发。
 下面让我们分析一下这两个函数在gcc4.6中的具体实现。

预备知识
1.引用折叠规则:
X& + & => X&
 X&& + & => X&
 X& + && => X&
 X&& + && => X&&

2.函数模板参数推导规则(右值引用参数部分):
 当函数模板的模板参数为T而函数形参为T&&(右值引用)时适用本规则。
 若实参为左值 U& ,则模板参数 T 应推导为引用类型 U& 。
 (根据引用折叠规则, U& + && => U&, 而T&& ≡ U&,故T ≡ U& )
 若实参为右值 U&& ,则模板参数 T 应推导为非引用类型 U 。
 (根据引用折叠规则, U或U&& + && => U&&, 而T&& ≡ U&&,故T ≡ U或U&&,这里强制规定T ≡ U )

3.std::remove_reference为C++0x标准库中的元函数,其功能为去除类型中的引用。
std::remove_reference<U&>::type ≡ U
 std::remove_reference<U&&>::type ≡ U
 std::remove_reference<U>::type ≡ U

4.以下语法形式将把表达式 t 转换为T类型的右值(准确的说是无名右值引用,是右值的一种)
static_cast<T&&>(t)
5.无名的右值引用是右值
 具名的右值引用是左值。
6.注:本文中 ≡ 含义为“即,等价于“。

std::move

函数功能

std::move(t) 负责将表达式 t 转换为右值,使用这一转换意味着你不再关心 t 的内容,它可以通过被移动(窃取)来解决移动语意问题。

 源码与测试代码

[cpp] view plain copy print?
01.template<typename _Tp>
02.  inline typename std::remove_reference<_Tp>::type&&
03.  move(_Tp&& __t)
04.  { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }  

[cpp] view plain copy print?
01.#include<iostream>
02.using namespace std;
03.
04.struct X {};
05.
06.int main()
07.{
08.    X a;
09.    X&& b = move(a);
10.    X&& c = move(X());
11.}  

代码说明
1.测试代码第9行用X类型的左值 a 来测试move函数,根据标准X类型的右值引用 b 只能绑定X类型的右值,所以 move(a) 的返回值必然是X类型的右值。
2.测试代码第10行用X类型的右值 X() 来测试move函数,根据标准X类型的右值引用 c 只能绑定X类型的右值,所以 move(X()) 的返回值必然是X类型的右值。
3.首先我们来分析 move(a) 这种用左值参数来调用move函数的情况。
4.模拟单步调用来到源码第3行,_Tp&& ≡ X&, __t  ≡ a 。

5.根据函数模板参数推导规则,_Tp&& ≡ X& 可推出 _Tp ≡ X& 。

6.typename std::remove_reference<_Tp>::type ≡ X 。
typename std::remove_reference<_Tp>::type&& ≡ X&& 。
7.再次单步调用进入move函数实体所在的源码第4行。
8.static_cast<typename std::remove_reference<_Tp>::type&&>(__t) ≡ static_cast<X&&>(a)
9.根据标准 static_cast<X&&>(a) 将把左值 a 转换为X类型的无名右值引用。
10.然后我们再来分析 move(X()) 这种用右值参数来调用move函数的情况。
11.模拟单步调用来到源码第3行,_Tp&& ≡ X&&, __t  ≡ X() 。
12.根据函数模板参数推导规则,_Tp&& ≡ X&& 可推出 _Tp ≡ X 。

13.typename std::remove_reference<_Tp>::type ≡ X 。
typename std::remove_reference<_Tp>::type&& ≡ X&& 。
14.再次单步调用进入move函数实体所在的源码第4行。
15.static_cast<typename std::remove_reference<_Tp>::type&&>(__t) ≡ static_cast<X&&>(X())
16.根据标准 static_cast<X&&>(X()) 将把右值 X() 转换为X类型的无名右值引用。
17.由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?
01./// forward (as per N3143)
02.template<typename _Tp>
03.  inline _Tp&&
04.  forward(typename std::remove_reference<_Tp>::type& __t)
05.  { return static_cast<_Tp&&>(__t); }
06.
07.template<typename _Tp>
08.  inline _Tp&&
09.  forward(typename std::remove_reference<_Tp>::type&& __t)
10.  {
11.    static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
12.    " substituting _Tp is an lvalue reference type");
13.    return static_cast<_Tp&&>(__t);
14.  }  

[cpp] view plain copy print?
01.#include<iostream>
02.using namespace std;
03.
04.struct X {};
05.void inner(const X&) {cout << "inner(const X&)" << endl;}
06.void inner(X&&) {cout << "inner(X&&)" << endl;}
07.template<typename T>
08.void outer(T&& t) {inner(forward<T>(t));}
09.
10.int main()
11.{
12.    X a;
13.    outer(a);
14.    outer(X());
15.    inner(forward<X>(X()));
16.}
17.//inner(const X&)
18.//inner(X&&)
19.//inner(X&&)  

代码说明
1.测试代码第13行用X类型的左值 a 来测试forward函数,程序输出表明 outer(a) 调用的是 inner(const X&) 版本,从而证明函数模板outer调用forward函数在将参数左值 a 转发给了inner函数时,成功地保留了参数 a 的左值属性。
2.测试代码第14行用X类型的右值 X() 来测试forward函数,程序输出表明 outer(X()) 调用的是 inner(X&&) 版本,从而证明函数模板outer调用forward函数在将参数右值 X() 转发给了inner函数时,成功地保留了参数 X() 的右值属性。
3.首先我们来分析 outer(a) 这种调用forward函数转发左值参数的情况。
4.模拟单步调用来到测试代码第8行,T&& ≡ X&, t  ≡ a 。

5.根据函数模板参数推导规则,T&& ≡ X& 可推出 T ≡ X& 。

6.forward<T>(t) ≡ forward<X&>(t),其中 t 为指向 a 的左值引用。

7.再次单步调用进入forward函数实体所在的源码第4行或第9行。

8.先尝试匹配源码第4行的forward函数,_Tp ≡ X& 。
9.typename std::remove_reference<_Tp>::type ≡ X 。
typename std::remove_reference<_Tp>::type& ≡ X& 。
10.形参 __t  与实参 t 类型相同,因此函数匹配成功。
11.再尝试匹配源码第9行的forward函数,_Tp ≡ X& 。
12.typename std::remove_reference<_Tp>::type ≡ X 。
typename std::remove_reference<_Tp>::type&& ≡ X&& 。
13.形参 __t  与实参 t 类型不同,因此函数匹配失败。
14.由10与13可知7单步调用实际进入的是源码第4行的forward函数。
15.static_cast<_Tp&&>(__t) ≡ static_cast<X&>(t) ≡ a。
16.inner(forward<T>(t)) ≡ inner(static_cast<X&>(t)) ≡ inner(a) 。
17.outer(a) ≡ inner(forward<T>(t)) ≡ inner(a)
再次单步调用将进入测试代码第5行的inner(const X&) 版本,左值参数转发成功。
18.然后我们来分析 outer(X()) 这种调用forward函数转发右值参数的情况。
19.模拟单步调用来到测试代码第8行,T&& ≡ X&&, t  ≡ X() 。
20.根据函数模板参数推导规则,T&& ≡ X&& 可推出 T ≡ X 。

21.forward<T>(t) ≡ forward<X>(t),其中 t 为指向 X() 的右值引用。

22.再次单步调用进入forward函数实体所在的源码第4行或第9行。
23.先尝试匹配源码第4行的forward函数,_Tp ≡ X 。
24.typename std::remove_reference<_Tp>::type ≡ X 。
typename std::remove_reference<_Tp>::type& ≡ X& 。
25.形参 __t  与实参 t 类型相同,因此函数匹配成功。
26.再尝试匹配源码第9行的forward函数,_Tp ≡ X 。
27.typename std::remove_reference<_Tp>::type ≡ X 。
typename std::remove_reference<_Tp>::type&& ≡ X&& 。
28.形参 __t  与实参 t 类型不同,因此函数匹配失败。
29.由25与28可知22单步调用实际进入的仍然是源码第4行的forward函数。

30.static_cast<_Tp&&>(__t) ≡ static_cast<X&&>(t) ≡ X()。
31.inner(forward<T>(t)) ≡ inner(static_cast<X&&>(t))  ≡ inner(X())。
32.outer(X()) ≡ inner(forward<T>(t)) ≡ inner(X())
再次单步调用将进入测试代码第6行的inner(X&&) 版本,右值参数转发成功。

33.由17和32可知源码中std::forward函数的具体实现符合标准,
 因为无论用左值a还是右值X()做参数来调用带有右值引用参数的函数模板outer,
 只要在outer函数内使用std::forward函数转发参数,
 就能保留参数的左右值属性,从而实现了函数模板参数的完美转发。
时间: 2024-10-09 23:54:16

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

C++11之右值引用:从左值右值到右值引用

C++98中规定了左值和右值的概念,但是一般程序员不需要理解的过于深入,因为对于C++98,左值和右值的划分一般用处不大,但是到了C++11,它的重要性开始显现出来. C++98标准明确规定: 左值是可以取得内存地址的变量. 非左值即为右值. 从这里可以看出,可以执行&取地址的就是左值,其他的就是右值. 这里需要明确一点,能否被赋值不是区分C++左值和右值的区别. 我们给出四个表达式: string one("one"); const string two("two&

C++11新特性:右值引用和转移构造函数

问题背景 [cpp] view plaincopy #include <iostream> using namespace std; vector<int> doubleValues (const vector<int>& v) { vector<int> new_values( v.size() ); for (auto itr = new_values.begin(), end_itr = new_values.end(); itr != end

表达式左值右值

左值右值是表达式的属性,该属性称为 value category.按该属性分类,每一个表达式属于下列之一: lvalue left value,传统意义上的左值 xvalue expiring value, x值,指通过“右值引用”产生的对象 prvalue pure rvalue,纯右值,传统意义上的右值(?) 而 xvalue 和其他两个类型分别复合,构成: lvalue + xvalue = glvalue general lvalue,泛左值 xvalue + prvalue = rva

C、C++差异之左值右值

C与C++在语法细节上还是有一些差异的,虽然一般情况下可能这些差异不足以造成结果的区别,但有些代码确实会有影响. 这次,主要总结下左值右值的差异. 在C中,很多左值运算符的结果都不再是左值,然而在C++中,只要逻辑上可行,左值运算符的结果仍然是左值.C++的这种方式,让运算符表达式之间的灵活性更大. 1.++i, 我们都习惯了在C++中,i 自加后返回自己:然而在C中i自加后,返回的是个临时的副本,即和i++是一样的,此结果不能作为左值,即(++i)=0非法.所以我想这就是为什么代码 中还是很多

C++11线程指南(四)--右值引用与移动语义

1. 按值传递 什么是按值传递? 当一个函数通过值的方式获取它的参数时,就会包含一个拷贝的动作.编译器知道如何去进行拷贝.如果参数是自定义类型,则我们还需要提供拷贝构造函数,或者赋值运算符来进行深拷贝.然而,拷贝是需要代价的.在我们使用STL容器时,就存在大量的拷贝代价.当按值传递参数时,会产生临时对象,浪费宝贵的CPU以及内存资源. 需要找到一个减少不必要拷贝的方法.移动语义就是其中一种. 2. 右值引用 此处介绍右值引用的目的,是为了实现后面的移动语义. 右值引用使得我们可以分辨一个值是左值

C++ 左值 右值

最近在研究C++ 左值 右值,搬运.收集了一些别人的资料,供自己记录和学习,若以后看到了更好的解释,会继续补充.(打“?”是我自己不明白的地方 ) 参考:<Boost程序库探秘——深度解析C++准标准库(第2版)> 9787302342731 左值:一个可以用来存储数据的变量,有实际的内存地址(变量名): 右值(非左值):“匿名”的“临时”变量,在表达式结束时生命周期终止,不能存放数据,可被修改,也可不被修改(const修饰). 鉴别左值右值: 左值可用取地址操作符&获取地址,右值不行

C++中的左值右值

对于 C++ 中的左值和右值,我们通常的说法是:当一个对象被用作右值的时候,用的是对象的值(内容):当对象被用作左值的时候,用的是对象的身份(在内存中的位置),这句话来自于 <C++ Primer 第五版> 第 121 页,那么,对于这句话,该作何理解呢?下面我想来谈谈我的看法: ISO C++03规定表达式必须是左值或右值之一,而在ISO C++11中,左值性被正式地扩充为更复杂的值类别,对于一个变量来说,与它相关的有两个部分:一是变量在内存中的地址,二是这个变量在内存中的地址中所存储的数据

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

c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的持久化对象,右值是指表达式结束时就不再存在的临时对象.所有的具名变量或者对象都是左值,而右值不具名.很难得到左值和右值的真正定义,但是有一个可以区分左值和右值的便捷方法:看能不能对表达式取地址,如果能,则为左值,否则为右值. 看见书上又将右值分为将亡值和纯右值.纯右值就是c++98标准中右值的概念,

【足迹C++primer】59、模板左值右值的转换

模板左值右值的转换 /** * 功能:模板左值右值的转换 * 时间:2014年7月27日08:18:06 * 作者:cutter_point */ #include<iostream> #include<algorithm> #include<utility> using namespace std; template<typename It> auto fcn(It beg, It end) -> typename remove_reference&

左值右值(转)

原文链接:http://blog.csdn.net/csdnji/article/details/169200 左值(lvalue)和右值(rvalue)是编程中两个非常基本的概念,但是也非常容易让人误解,看了很多文章,自我感觉真正将这个问题讲的很透彻的文章还没有看见,所以自告奋勇来尝试一下.如果左值右值的概念不是非常清楚的话,它们迟早会像拦路虎一样跳出来,让你烦心不已,就像玩电脑游戏的时候每隔一段时间总有那么几个地雷考验你的耐性,如果一次把所有地雷扫尽就好了.:) 左值(lvalue)和右值(