C++避坑指南(六)

关注公众号【高性能架构探索】,第一时间获取最新文章;公众号内回复【pdf】,免费获取经典书籍

引用折叠

引用折叠指的是在模板参数以及 auto 类型推导时遇到多重引用时进行的映射关系,我们先从最简单的例子来说:

template <typename T> void f(T &t) { } void Demo() {   int a = 3;   f<int>(a);   f<int &>(a);   f<int &&>(a); }

T实例化为int时,函数变成了:

void f(int &t);

但如果T实例化为int &int &&时呢?难道是这样吗?

void f(int & &t); void f(int && &t);

我们发现,这种情况下编译并没有出错,T本身带引用时,再跟参数后面的引用符结合,也是可以正常通过编译的。这就是所谓的引用折叠,简单理解为“两个引用撞一起了,以谁为准”的问题。引用折叠满足下面规律:

左值引用短路右值引用

简单来说就是,除非是两个右值引用遇到一起,会推导出右值引用以外,其他情况都会推导出左值引用,所以是左值引用优先。

& + & -> & & + && -> & && + & -> & && + && -> &&

auto &&

这种规律同样同样适用于auto &&,当auto &&遇到左值时会推导出左值引用,遇到右值时才会推导出右值引用:

auto &&r1 = 5; // 绑定常量,推导出int && int a; auto &&r2 = a; // 绑定变量,推导出int & int &&b = 1; auto &&r3 = b; // 右值引用一旦绑定,则相当于普通变量,所以绑定变量,推导出int &

由于&&&优先级高,因此auto &一定推出左值引用,如果用auto &绑定常量或将亡对象则会报错:

auto &r1 = 5; // ERR,左值引用不能绑定常量 auto &r2 = GetAnObj(); // ERR,左值引用不能绑定将亡对象 int &&b = 1; auto &r3 = b; // OK,左值引用可以绑定右值引用(因为右值引用一旦绑定后,相当于左值) auto &r4 = r3; // OK,左值引用可以绑定左值引用(相当于绑定r4的引用源)

右值引用传递时失去右性

前面的章节笔者频繁强调一个概念:右值引用一旦绑定,则相当于普通的引用(左值)。

这也就意味着,“右值”性质无法传递,请看例子:

void f1(int &&t1) {} void f2(int &&t2) {   f1(t2); // 注意这里 } void Demo() {   f2(5); }

Demo函数中调用f2f2的参数是int &&,用来绑定常量5没问题,但是,在f2函数内,t2是一个右值引用,而右值引用一旦绑定,则相当于左值,因此,不能再用右值引用去接收。所以f2内部调f1的过程会报错。这就是所谓“右值引用传递时会失去右性”。

那么如何保持右性呢?很无奈,只能层层转换:

void f1(int &&t1) {} void f2(int &&t2) {   f1(std::move(t2)); // 保证右性 } void Demo() {   f2(5); }

但我们来考虑另一个场景,在模板函数中这件事会怎么样?

template <typename T> void f1(T &&t1) {} template <typename T> void f2(T &&t2) {   f1<T>(t2); } void Demo() {   f2<int &&>(5); // 传右值   int a;   f2<int &>(a); // 传左值 }

由于f1f2都是模板,因此,传入左值和传入右值的可能性都要有的,我们没法在f2中再强制std::move了,因为这样做会让左值变成右值传递下去,我们希望的是保持其左右性。

但如果不这样做,当我向f2传递右值时,右性无法传递下去,也就是t2int &&类型,但是传递给f1的时候,t1变成了int &类型,这时t1t2的引用(就是左值引用绑定右值引用的场景),并不是我们想要的。那怎么解决,如何让这种左右性质传递下去呢?就要用到模板元编程来完成了:

template <typename T> T &forward(T &t) {   return t; // 如果传左值,那么直接传出 } template <typename T> T &&forward(T &&t) {   return std::move(t); // 如果传右值,那么保持右值性质传出 } template <typename T> void f1(T &&t1) {} template <typename T> void f2(T &&t2) {   f1(forward<T>(t2)); } void Demo() {   f2<int &&>(5); // 传右值   int a;   f2<int &>(a); // 传左值 }

上面展示的是std::forward的一个示例型的代码,便于读者理解,实际实现要稍微复杂一点。思路就是,根据传入的参数来判断,如果是左值引用就直接传出,如果是右值引用就std::move变成右值再传出,保证其左右性。std::forward又被称为“完美转发”,意义就在于传递引用时能保持其左右性。

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/1258

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2022年11月2日 下午12:17
下一篇 2022年11月2日 下午12:19

相关推荐