200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > C++11右值引用与引用坍缩/完美转发

C++11右值引用与引用坍缩/完美转发

时间:2019-09-16 12:50:45

相关推荐

C++11右值引用与引用坍缩/完美转发

右值

目录

左值和右值右值引用引用坍缩完美转发

左值和右值

左值和右值从最基本的书面意思上理解就是:

表达式左边的值为左值,表达式右边的值为右值

int x = 1;int y = 2;int z = x + y;

这里x、y、z是左值,1、2和x+y的结果也是右值,但这样的分类并不标准:

int a = 1;int b = a;

这时,a是不是可以看成左值也可以看成右值;与我们上面的简单分类就矛盾了。

C++中的左值一般是指一个指向特定内存的具名对象,它有一个具体稳定的内存地址和较长的生命周期。

而右值则是不指向稳定内存地址的匿名值,即不具名对象。

那么基于这个特性,我们可以用&引用符号来判断左值和右值,能取到地址的为左值,否则为右值。

不过,C++标准的对类型的定义更复杂:

lvalue是通常可以放在等号左边的表达式,左值rvalue是通常只能放在等号右边的表达式,右值glvaluegeneralized lvalue,广义左值xvalueexpiring lvalue,将亡值prvaluepure rvalue,纯右值

我们先看lvalueprvalue:

lvalue就是上面提到过的,可以通过取地址来判断的左值,如:

变量,函数返回左值引用的表达式:++xx = 1

📌这里x++是右值,而++x是左值;因为在后置++操作中编译器首先会生成一个x的临时复制,然后才对x递增;而++x则是直接对x递增然后返回自身,即原地操作

字符串字面量:”hello“

而纯右值prvalue是没有标识符的,不能取地址,一般也称为”临时对象“,比如:

返回非引用类型表达式:x++x + 1除字符串字面量之外的字面量:42true

在C++11之前,右值可以绑定到常左值引用的参数,如const T&;但不能绑定到非常值左值引用,如T&。

这里绑定可以简单理解为赋值,即const int& ref = 42

右值引用

从C++11开始,C++就引入了一种新的引用类型——右值引用;右值引用的形式是T&&

右值引用,顾名思义就是一种引用右值的方法。

那么,引用右值将要解决的问题是什么?

简单来看,有了右值引用,我们就可以把右值绑定到右值引用上,进而延长右值的生命周期

我们先来看一下移动语义,移动语义的原理是允许在对象之间转移资源的所有权,而不是进行深拷贝;我们通常用std::move来实现移动语义。

左值引用在传递参数或返回值时,通常调用拷贝构造函数或赋值构造函数,来创建对象的副本。

而右值引用引入了移动构造函数和移动赋值函数,它们可以直接将对象的所有权转移到另一个容器中

class MyData {public:MyData() {}// 移动构造函数MyData(MyData&& other) noexcept {}// 移动赋值函数MyData& operator=(MyData&& other) noexcept {}};

所以右值引用的作用不只是延长右值的生命周期,更重要的是这种机制可以用来减少对象的拷贝

了解了右值引用的作用后,再来看std::move,它的作用就是将传入的参数强制转换成右值引用;那就可以有这样的表达式std::move(ptr),它可以看作一个有名字的右值,为了和纯右值prvalue做区分,C++就把这种表达式叫做xvalue(将亡值)

但是xvalue还是和左值lvalue不同,不能取地址。

上面我们讨论的这些左值、纯右值、将亡值,都属于值类别;而值类型才是指我们平时常见的int,double,枚举这些类型。

引用坍缩

常左值引用既可以引用左值又可以引用右值,是一种几乎全能的引用;不过其常量性还是限制了作用范围。

C++11标准中有着一个能称为万能的引用,它的形式是:

T &&auto &&

所谓的万能引用是因为发生了类型推导,T&&auto&&的初始化过程中都会发生类型的推导

C++中添加了一套引用叠加推导的规则——引用坍缩,有着如下规则:

下面对上述规则给出例子:

左值引用和右值引用坍缩成左值引用:

template <typename T>void foo(T& arg) {// 在这里,arg被视为左值引用,即使传入的是右值}int main() {int x = 42;foo(x); // T被推导为int,arg是int&,因此arg被视为左值引用foo(10); // T被推导为int,arg是int&,因此arg被视为左值引用return 0;}

右值引用和左值引用坍缩成右值引用:

template <typename T>void bar(T&& arg) {// 在这里,arg被视为右值引用,即使传入的是左值或右值}int main() {int x = 42;bar(x); // T被推导为int&,arg是int& &&,因此arg被视为int&bar(10); // T被推导为int,arg是int&&,因此arg被视为右值引用return 0;}

完美转发

之前提到了万能引用的语法和推导规则,现在来谈一下它的用途,完美转发。

我们先来看一个转发函数模板:

template<class T>void show_type(T t){std::cout << typeid(t).name() << std::endl;}template<class T>void normal_forwarding(T t){show_type(t);}int main(){std::string s = "hello world";normal_forwarding(s);}

normal_forwarding是一个转发函数模板,可以做一些字符串的转发任务;不过因为是按值转发,会发生一次临时对象的复制。

为了解决这个问题可以把normal_forwarding(T t)替换为normal_forwarding(T& t),这样能够避免临时对象的复制。

不过又会带来另一个问题,如果传递的是一个右值,将无法编译。

这时还可以将normal_forwarding(T& t)替换为normal_forwarding(const T& t),因为常量左值引用时可以引用右值;但是如果之后要修改这个字符串,又会编译错误。

万能引用的出现解决了这个问题,对于万能引用的形参来说:

如果实参是左值,则形参会被推导为左值引用;如果实参是右值,则形参会被推导为右值引用;

所以无论传递的是左值还是右值都可以被转发,不会产生多余的拷贝。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。