More than code

More Than Code
The efficiency of your iteration of reading, practicing and thinking decides your understanding of the world.
  1. 首页
  2. daily
  3. 正文

Daily C/C++ 右值引用

2021年6月8日 536点热度 0人点赞 0条评论

右值引用

还是谈一谈我对右值引用的理解

有关右值引用的讲解网上有很多,这里就不在一一罗列了

右值引用,毫无疑问是用来解决冗余拷贝的问题的。

比如在拷贝构造函数里实现了深拷贝,那么在某些情况下我们就有可能重复的进行大量资源的拷贝,造成资源的浪费

C++的编译器有对这方面的优化,叫做RVO(return value optimization)

我们在函数返回的时候,不进行一次次的拷贝构造,而是直接将对象构造在他应该放的地方,从而省略了中途冗余的拷贝

但是这毕竟只是编译器做的优化,不能完全帮助我们优化性能,所以有的时候这些工作还是需要我们自己来做的

于是右值引用在C++11中应运而生,右值引用的作用就是帮我们延长临时变量的生命周期,从而直接从构造的临时变量中获取资源

这里不详细讲解右值引用的具体语法,而是谈几个自己在理解右值引用过程中的易错点

首先,引用,无论是左值引用,还是右值引用,他们都只是一个别名,他们是原始对象的一个别称。

引用自身是没有实际的资源的,所以在我们使用引用类型的变量之前,首先想一想他们引用的是谁,这个对象的另一个别称是谁。

否则你可能错把引用理解成了实际的变量,从而导致代码的错误

一个很常见的错误就是在一个函数中,返回对局部变量的引用

虽然返回后,这个引用还存在,但是他所引用的对象已经在函数退出时析构掉了,所以对这个引用变量解引用所产生的行为是未定义的

用一个图来解释一下值类别(value catagory)

20210608111555

这里我们只看下面三个就可以,lvalue是左值,xvalue是将亡值,prvalue是纯右值

左值就是我们等号左边的值,可以被取地址的值

纯右值就是没名字的值,一般是字面量或者临时对象

将亡值就是即将被销毁的值,也可以看做是有名字的纯右值,std::move就是将左值的类型转换为了将亡值,因为他是有名字的右值

有一个规则要注意,就是临时对象的生命周期延长规则

如果一个prvalue被绑定到一个引用上,那么他的生命周期会延长到和这个引用变量一样长

但是这条规则对xvalue,也就是将亡值是不适用的

但是这是为什么呢?我的理解是这样的

右值引用本质还是引用,需要一个绑定到一个右值上

但是对于将亡值来说,他有自己的生命周期,我们不能简单的让右值引用去随意改变他的生命周期

而对于纯右值来说,他的生命周期是这条语句,所以我们就可以把这个纯右值绑定到我们的右值引用上

那既然如此,我们引入将亡值的目的是什么呢?或者用std::move将一个左值转换为将亡值的目的是什么呢?

调用移动构造函数,注意移动构造函数接收的是右值,我们这里说的也是右值引用,而不是什么将亡值引用或者纯右值引用

看上面的图也可以看到,右值分为纯右值和将亡值

所以我们在使用类似Point(1, 1) 或者Point(std::move(point1))的时候,我们使用的是移动构造函数,这样也就消除了中间临时变量造成的冗余拷贝

然后再来说一说函数返回引用的问题

之前有讲到过,返回对局部变量的引用是错误的,但是这不代表我们不可以返回引用的类型

比如一个对象

struct Beta {
  Beta_ab ab;
  Beta_ab const& getAB() const& { return ab; }
  Beta_ab && getAB() && { return move(ab); }
};

我们在使用类似Beta ab = Beta().getAB()的时候,使用的就是移动构造

因为这里我们将ab变为将亡值,作为右值引用返回给了调用者,调用者会调用移动构造函数处理之前将亡值的资源

后面的修饰符用来说明函数作为右值时才调用

再看这个情况

class Widget {
private:
    DataType values;

public:
    DataType& data() &  { return values; } 
    DataType  data() && { return std::move(values); }
};

auto values = makeWidget().data();

可以发现和上面的返回类型是不同的

这里我们返回的是值,而不是引用

在这个过程中,由于我们返回的是move(ab),所以临时返回值会首先通过移动构造函数创建,然后回到调用者,makeWidget().data()是纯右值,所以我们会再次调用移动构造函数,创建values

其实应该还可以让函数直接返回值,然后我们使用右值引用捕捉这个临时的返回值

不过实际在使用的过程中,因为我们有返回值优化,所以不需要自己主动的添加std::move,这样还会多进行一次移动构造

有关万能引用和完美转发我们下次再说,这里简单总结一下

分析或者写代码时,我们主要看好std::move和函数参数

20210608121845

20210608121853

move帮我们生成将亡值,用于调用移动构造函数或者移动赋值

纯右值自己就可以调用,所以不需要我们主动去move

最后还有一个比较有意思的结论,对引用的引用仍然还是引用,因为我们的引用是别名,这个结论在右值引用下仍然成立

函数返回右值引用,(返回引用也是)需要返回的是非局部变量,那么这个非局部变量就需要我们主动去使用move,否则的话他不会被判定为右值

标签: c++
最后更新:2021年6月8日

sheep

think again

点赞
< 上一篇
下一篇 >

文章评论

取消回复

COPYRIGHT © 2021 heavensheep.xyz. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS