C++右值引用和完美转发

右值引用

1. 基本含义

C++中所有的表达式都是左值或者右值。右值编译器管它叫rvalue,左值编译器叫它lvalue。具体定义没太研究,但是他们有以下区别:

  1. 左值与右值的区别是左值具名,可以取址 并访问
  2. 右值不具名,通常是临时的变量,常数等。不可取址,仅在当前作用域有效,可以被移动。

一些常见的左值和右值的例子如下图。

2. 用处

首先,左值,左值引用晓得吧。左值引用的一个example如下。通过左值引用,我们可以:

  • 在函数内外共用同一个变量,可以实现函数内外的值同步改变.
  • 也可以减少实例的拷贝带来的时间和内存的消耗。
class A{
    int a;
public:
    A(int num){
        a = num;
    }
    A& operator=(const A& a){}
    void set(A &a){
        this->a = a.a;
    }
}

int main(){
    A a(3);
    A b(2);
    a.set(b);
    return 0;
}

在上述example中,b就是一个左值,是一个具名变量,而如果你直接调用func(A())就会报错,因为A()是一个临时变量(右值),在传参结束后就被销毁了,因此也不能引用了。此时就需要引入右值引用,右值引用在形参中用&&表示

class A{
    int a;
public:
    A(int num){
        a = num;
    }
    A& operator=(const A& a){}
    void set(A &a){
        this->a = a.a;
    }

    void set(A &&a){
        this->a = a.a;
    }

    void func(int && num){
        a = num;
    }
}

int main(){
    A a(2);
    a.set(A(3));
    a.func(3); // 3 是常数,也是右值
    return 0;
}

当然,上述的两个set函数在功能上是有重复的,我们完全可以把他们合并,这里就要提到std::move()函数了。

3. std::move()让左值变成右值

std::move()是让一个左值变成右值,比如下面的代码也是完全合法的。

class A{
    int a;
public:
    A(int num){
        a = num;
    }
    A& operator=(const A& a){}

    void set(A &&a){
        this->a = a.a;
    }

    void func(int && num){
        a = num;
    }
}

int main(){
    int c = 4;
    A a(2);
    A b(3);
    a.set(A(3));
    a.set(std::move(b)); //没有std::move会报错
    a.func(3); // 3 是常数,也是右值
    a.func(std::move(c)); //没有std::move会报错
    return 0;
}

读完这段代码再想想,仅仅依靠左值引用,上面的功能是无法实现的。

4. 用右值引用实现类型推断

当一个函数的形参是模板类型的右值引用时,那么实参的类型会进行自动推断(而不要求必须是右值),比如下面代码中的调用都是合法的。注意在正常情况下如果一个形参是右值引用,那么给函数传递一个左值是会有编译错误的,但是使用模板类型的右值引用就不会有这个问题

class A{
    int a;
public:
    A(int num){
        a = num;
    }
    A& operator=(const A& a){}

    template<typename T>
    void set(T &&a){
        this->a = a.a;
    }
}
int main(){
    A a(3);
    A b(4);
    a.set(b); // 合法,b是一个左值,传递进函数后依然是左值
    a.set(A(4)); // 合法传递一个右值
}

5. 完美转发

一个表达式是左值还是右值,这个性质是会发生变化的,在上述例子中,实参A(3)虽然是右值,但是一旦传递给函数后,在set()函数内部,它就变成了左值,因为它现在不在是一个临时变量,而是成了set函数内部的局部变量。

完美转发指的是表达式被传递时能够原封不动地被转发,这里所说的原封不动,指的是变量的值、是否 const、是否为左/右值的属性均不能发生改变。完美转发需要依靠三样东西来同时完成:模板类型、std::forward()、右值引用。下面是一个example:

class A{
    int a;
public:
    A(int num){
        a = num;
    }
    A& operator=(const A& a){}

    template<typename T>
    void set(T &&a){
        this->a = a.a;
        set2(std::forward(a));
    }
    
    template<typename T>
    void set2(T &&a){}
}

在上述函数中,set函数的调用传参可以是左值、也可以是右值,可以含有const,可以不含const,无论是何种情况调用都是合法的,并且set函数调用set2时,会完美转发a,如果传递进set函数的是右值,那么传递给set2的依然是右值,其他属性也一样。

6. 左值和右值模板的类型推断规则

& &&
T& & &
T&& & &&

上述表格中T&代表形参是左值引用的模板类型,T&&代表形参是右值引用的模板类型,表头&和&&分别表示实参是左值还是右值,对应表中实际的类型,可以看到

  1. 当形参为左值时,无论传入的参数是什么,它实际都会变成左值。
  2. 当形参为右值时,实际的参数由实参决定

注意

右值引用过程中涉及到一个可能的隐患,即内存有效性问题。这里提供了一个例子:

在上述拷贝构造中,为了安全,我们必须将实参str的data设置为null,方式构造函数调用结束后str被析构,它申请的内存变为无效的情况发生。例子详见https://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/index.html

参考文献

  1. 移动语义与完美转发 | Universal Reference https://www.sczyh30.com/posts/C-C/cpp-move-semantic/
  2. 右值引用于转移语义:https://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/index.html
  3. 移动语义与完美转发 https://codinfox.github.io/dev/2014/06/03/move-semantic-perfect-forward/
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容