阅读此文章之前,默认读者会C++的基础语法和模板
目录
导论
什么是万能引用与完美转发?
研究这个问题之前,我们先搞清楚为什么需要这些新的特性。
当一个左值传递给一个函数时,参数会以左值引用的方式进行传递;
当一个右值传递给一个函数时,参数会以右值引用的方式进行传递。
那么什么是左值和右值?
-
左值(Lvaule)指表达式结束后依旧存在的对象。
- 右值(Rvalue)指表达式结束后便销毁的临时对象。
左值引用就是给左值取别名,实际上指向了同一片内存空间。
右值引用就是给右值取别名,实际上也指向了同一片内存空间。
那么区别在哪里?
右值有着表达式结束后对象便销毁的特性,但如果我们使用了右值引用,
那么编译器便不会立刻销毁对应的空间。
如此为了实现某一个要求,便有了万能引用与完美转发。
1.万能引用(Universal Reference)
万能引用便是在左值引用和右值引用的基础上,利用模板自动类型推导的特性,自动判断传入的是左值引用还是右值引用。
语法:T&& Val
注意:
template<typename T> //函数模板
void f( T&& val); // 这里T的类型需要推导,所以是万能引用
template<typename T> //类模板
class Ttpe
{
Type(Type&& val); // Type是一个特定的类型,不能类型推导,所以&&表示具体的右值引用
};
只有当发生自动类型推导时,
T&&
才是万能引用
1.1引用折叠(Reference Collapsing)
由于万能引用是自动类型推导,便自然衍生出了一个特性——引用折叠。
概念:
由于存在T&&这种未定的引用类型,当它作为参数时,有可能被一个左值引用或右值引用的参数初始化,这是经过类型推导的T&&类型,相比右值引用(&&)会发生类型的变化,这种变化就称为引用折叠。(《深入应用C++11-代码优化与工程级应用》 --- 祁宇 P68 )
如果两个引用中至少其中一个引用是左值引用,那么折叠结果就是左值引用;否则折叠结果就是右值引用。
规则:
内层引用类型 | 外层引用类型 | 结果 |
---|---|---|
T& | & | T& |
T& | && | T& |
T&& | & | T& |
T&& | && | T&& |
2.完美转发(Perfect Forwarding)
当我们使用了万能引用,但需要转发参数给其他函数时,会丢失引用性质,由此便有了完美转发。
C++11提供了完美转发函数。它可以在模板函数内给另一个函数传递参数时,将参数类型保持原本状态传入。
语法: std::forward<T>
template<class T>
void func(T&& obj){
_func(std::forward<T>(obj));
}
案例:
C++标准模板库(STL)就有完美的运用万能引用与完美转发的例子。
为了方便理解与运用,这里我用自制的循环队列来讲解。
#include <iostream>
using namespace std;
template<class T>
struct SqQueue
{
T data[10] = { };
int front, rear;
int size;
SqQueue()
{
front = rear = size = 0;
}
template<class... V>
void emplace(V&&... Val)
{
new (&data[rear]) T(forward<V>(Val)...);
this->rear = (this->rear + 1) % Max;
size++;
}
};
我们只将重点放在这里的emplace函数上。
此函数的用意便是在队列的“rear”也就是尾节点内插入一个全新的数据。
template<class… V1>表示这是一个函数模板,
“…”表示可以有多个参数,
形参列表里就是上面讲的万能引用,
forward<V>(Val…)是完美转发,可以在模板函数内给另一个函数传递参数时,将参数类型保持原本状态传入,
new (指针) T (args)相当于对已有地址进行new的初始化,强行调用构造函数。
由此,便可直接在使用时emplace函数时直接构造一个对象,而省去了拷贝操作,降低了时间复杂度。和STL里的emplace同理,这便是万能引用与完美转发的具体运用。
本人才疏学浅,如有错误请指正,感激不尽!
以上都是群内巨佬——三色牌告诉我的,有问题可以在群里找他,我是菜鸡
群号:762514085