前言
不是所有的&&
都是右值引用!
1 | void f (Widget&& param); // 右值引用 |
好了,看完CPU烧了,到底是不是右值引用呀,我也不知道呀。
实际上, “ T&&
”有两种不同的含义。其中一种含义,理所当然,是右值引用。正如期望,它们仅仅会绑定到右值,而其主要的存在理由,在于识别出可移对象。“ T&&
“的另一种含义,则表示其既可以是右值引用,亦可以是左值引用。它可以绑定到右值,也可以像左值引用一样,也可以绑定在const
对象或者非const
对象,也可以绑定在volatile
对象或者非volatile
对象,它灵活度高到被成为万能引用。
万能引用的场景
- 函数模板的推导
- auto声明
1 | auto&& var2 = van; // 不是右值引用 |
这两个场景的共同之处:都涉及型别推导。如果看到了T&&
但是没有型别推导,那么就可以确定是右值引用。
万能引用的条件
万能引用的特点是它们具有两个条件:
必须在模板内进行型别推导:这意味着类型 T 必须在模板函数或模板类内部通过类型推导而不是显式指定。这是因为模板会根据实际传递给函数或类的参数类型来推导 T 的类型。
型别声明的形式必须是
T&&
:这表示右值引用的声明必须以 T&& 的形式出现,以指示它可以成为万能引用。
如果这两个条件同时满足,那么右值引用就被认为是万能引用。
这也就是为什么上面提到的void f(std: :vector<T>&& param);
是右值引用而不是万能引用。它虽然涉及型别推导,但它的型别声明形式不是 T&&
,而是 std::vector<T>&&
。因此,这只是一个右值引用,而不是万能引用。
即使只是一个const
的存在,也不满足条件2,就成为了右值引用。
1 | template<typename T> |
如果在一个模板内看到一个函数形参为T&&
也不能想当然的认为它是万能引用。看下面的例子
1 | using namespace std; |
push_back
的形参满足条件,但是条件一:型别推导,它并没有涉及。因为该函数作为vector
类的一部分,如果没有特定vector
类实例,就没有该函数(C++类的基础)。如果给定一个vector
实例,那么push_back
的形参就会被具体化。比如给定一个实例:
1 | std::vector<Entity> entity; |
vector模板就会被具化为:
1 | class vector<Entity, allocator<Entity>>{ |
可以看到,push_back
函数并没有涉及类型推导。因此其参数不是万能引用。
1 | template<class T , class Allocator = allocator<T>> |
这里的args
涉及到了类型推导,因此是万能引用。
多说一点
有些人不清楚template<class... Args>
中的...
是什么,其实很简单,”…” 是用来表示可变参数模板的语法。它用于表示函数或类模板可以接受可变数量的参数。具体来说,Args
是一个参数包,它可以包含零个或多个模板参数。Args&&... args
表示您可以将任意数量的参数传递给 emplace_back
函数,每个参数都会被表示为 Args
类型的右值引用。这种语法通常用于实现”完美转发”。(后面会提到)
而前面提到的另一种场景,auto声明,在C++11
中不怎么普遍。而在C++14
中,它现身的机会就更多了,因为C++14
中的lambda
表达式中可以声明auto&&
的形参。
万能引用的作用
这种引用在模板编程中非常有用,因为它们能够保持对传递给它们的参数的左值或右值性质,并且可以实现完美转发,使它们适用于各种不同类型的参数。