右值引用

右值引用是个效率工具,减少拷贝。
C++11标准提出的一类数据类型。用于实现转移语义(move semantic)与完美转发(perfect forwarding)

1. 几个定义

int i   = 5;                // i为左值, 5为右值
int &j  = i;                // j为左值引用
int &&k = std::move(i+5);   // k为右值引用

引用(reference)是指绑定到内存中的相应对象上。左值引用是绑定到左值对象上;右值引用是绑定到临时对象上。

设X为任何一种数据类型,则定义X&&是到数据类型X的右值引用(rvalue reference to X)。
传统的引用类型X&被称为左值引用(lvalue reference to X)。

右值引用与左值引用的变量都不能悬空(dangling),也即定义时必须初始化从而绑定到一个对象上。

2. 左值和右值

  1. 左值是对应(refer to)内存中有确定存储地址的对象的表达式的值,而右值是所有不是左值的表达式的值。
  2. 右值可以是字面量、临时对象等表达式。
  3. 左值与右值的根本区别在于是否允许取地址&运算符获得对应的内存地址。

3. 转移语义与std::move

C++在用临时对象或函数返回值给左值对象赋值时会产生拷贝,效率低。
如果把临时对象的内容直接移动(move)给被赋值的左值对象,效率改善将是显著的。这就是移动语义的来源。

所以转移语义与拷贝语义相对,类似电脑上文件的剪切与复制。

区分转移语义与拷贝语义用右值引用,有右值引用就是转移语义。

标准库函数std::move可以把左值或临终值,转化为右值引用。

有一点值得注意的时具名的右值引用被当作左值。

例子如下:

#include <iostream>
#include <vector>
#include <utility>

void process_value(int& i) {
    std::cout << "LValue processed: " << i << std::endl;
}

void process_value(int&& i) {
    std::cout << "RValue processed: " << i << std::endl;
}

int main()
{
    int i = 5;    
    int &j = i;   
    int &&k = std::move(i+5);

    process_value(j);               // LValue
    process_value(i + 5);           // RValue
    process_value(std::move(i));    // RValue. std::move把左值转化为右值引用
    process_value(k);               // LValue. 具名的右值引用被当作左值

    return 0;
}

结果:

LValue processed: 5
RValue processed: 10
RValue processed: 5
LValue processed: 10

上面只是简单的例子,用到类里面,与传统的拷贝赋值运算符(copy assignment)成员函数、拷贝构造(copy ctor)成员函数对应,
移动语义需要有移动赋值(move assignment)成员函数、移动构造(move ctor)成员函数的实现机制。
可以通过函数重载来确定是调用拷贝语义还是移动语义的实现。

上面的例子似乎只是说明了转移语义和std::move的用法,并没有看到转移语义带来的效率提升。
下面列举一个效率提升的例子:

#include <iostream>
#include <vector>
#include <utility>
#include <string>

int main()
{
    std::string str = "Hello";
    std::vector<std::string> v;

    v.push_back(str);               // copy. use push_back(const T&) 
    std::cout << "After copy, str is \"" << str << "\"\n";

    v.push_back(std::move(str));    // move. use push_back(T&&)

    std::cout << "After move, str is \"" << str << "\"\n";

    std::cout << "The contents of the vector are : " << std::endl
        << v[0] << std::endl
        << v[1] << std::endl;

    return 0;
}

结果:

After copy, str is "Hello"
After move, str is ""
The contents of the vector are :
Hello
Hello

第1种使用常量左值引用形式的push_back,第2种使用右值引用形式的push_back
第1种用法,涉及到string的拷贝,且对原str不产生影响。
第2种用法,使用了转移语义,直接把原来的内容转移到vector里去,被转移的str对象处在一个“合法而未指定”的状态,所以为""。这样就不用涉及string的拷贝,所以提高了效率。

4. 完美转发与std::forward

前面提到,具名的右值引用会被当作左值。
下面的fn()的参数可以是右值引用,fn()里调用了子函数overloaded(), fn()把右值引用参数转发给子函数overloaded()
如果没有完美转发,由于具名的右值引用会被当作左值,子函数overloaded()接收到的参数始终是左值。
本可以接收右值引用参数的overloaded()在这时没有提高传参效率。
std::forward是用来做完美转发的函数,对原来的参数做原样转发。

#include <iostream>
#include <vector>
#include <utility>
#include <string>

// function with lvalue and rvalue reference overloads:
void overloaded(const int& x)   { std::cout << "[lvalue]"; }
void overloaded(int&& x)        { std::cout << "[rvalue]"; }

// function template taking rvalue reference to deduced type:
template <class T> 
void fn(T&& x) {
    overloaded(x);                   // always an lvalue
    overloaded(std::forward<T>(x));  // rvalue if argument is rvalue
}

int main()
{
    int a;

    std::cout << "calling fn with lvalue: ";
    fn(a);
    std::cout << '\n';

    std::cout << "calling fn with rvalue: ";
    fn(0);
    std::cout << '\n';

    return 0;
}

结果:

calling fn with lvalue: [lvalue][lvalue]
calling fn with rvalue: [lvalue][rvalue]

overloaded()可以接收左值引用参数,也可以接收右值引用参数。
第1次,fn()的参数是左值引用,overloaded(x);调用的是左值引用的函数,overloaded(std::forward<T>(x))调用的也是左值引用的函数。
第2次,fn()的参数是右值引用,overloaded(x);调用的还是左值引用的函数,overloaded(std::forward<T>(x))调用的则是与fn()的参数一样的右值引用的函数。参数得到了完美转发。

为什么要把fn()写成模板函数?
因为加了模板,fn()就也可以使用左值去调用了,去掉template <class T>,fn()只接受右值引用的参数。
这里面涉及到引用折叠原则:

  1. T& & (引用的引用) 被转化成 T&
  2. T&& & (rvalue的引用)被传化成 T&
  3. T& && (引用作rvalue) 被转化成 T&
  4. T&& && 被转化成 T&&

所以左值的参数传过来T&& &被转化为T&,右值参数传过来T&& &&被转化成 T&&,所以这样写之后,fn()就既可以接收左值引用参数,也可以接收右值引用参数了。

5. 参考

https://zh.wikipedia.org/wiki/%E5%8F%B3%E5%80%BC%E5%BC%95%E7%94%A8
https://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/index.html
https://zh.cppreference.com/w/cpp/utility/move
http://www.cplusplus.com/reference/utility/forward/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • 本文根据众多互联网博客内容整理后形成,引用内容的版权归原始作者所有,仅限于学习研究使用,不得用于任何商业用途。 左...
    深红的眼眸阅读 11,259评论 1 12
  • 本文原名《Rvalue Refernces, Move Semantics, and Perfect Forwar...
    金戈大王阅读 4,087评论 2 6
  • C++右值引用 右值引用应该是C++11引入的一个非常重要的技术,因为它是移动语义(Move semantics)...
    小白将阅读 2,180评论 2 13
  • 每次回家都好像搬家一样,大包小包塞满一车,这个时候才知道车子的后备箱小了。 国庆长假快过去了,我们想想还是先走吧,...
    深圳的早晨阅读 835评论 0 3
  • 美好的一天从大鸟原创壁纸开始。 点击图片,长按保存,设为壁纸。 设置壁纸时,可以手动调整一下图片,以匹配自己的屏幕...
    大鸟8wo阅读 331评论 1 4