什么是万能引用
“万能引用”是 Scott Meyers 在他的书《Effective Modern C++》中引入的术语,用于描述某些模板参数的行为,这种模板参数可以绑定到几乎任何类型的参数,无论是 lvalue 还是 rvalue。正式的名称是"转发引用"(forwarding reference)。
一个万能引用的定义形式如下:
1template<typename T>
2void function(T&& arg);
这里,T&&
不仅仅是一个普通的右值引用。根据传递给函数的参数类型,它可能会表示一个 lvalue 引用或一个 rvalue 引用。这是通过模板参数推导和引用折叠规则实现的。
考虑以下情况:
- 传递 lvalue:
1int a = 42;
2function(a); // 'a' is an lvalue
在这种情况下,T
被推导为 int&
,所以 T&&
实际上变为 int& &&
。根据引用折叠规则,它折叠为 int&
。因此,arg
是一个指向 a
的 lvalue 引用。
- 传递 rvalue:
1function(42); // '42' is an rvalue
在这种情况下,T
被推导为 int
,所以 T&&
保持为 int&&
。因此,arg
是一个 rvalue 引用。
万能引用的一个主要用途是实现“完美转发”,即在模板中将参数转发到其他函数,同时保持其原始值类别(lvalue 或 rvalue)。这通常与 std::forward
函数模板结合使用,如:
1template<typename T>
2void forwarder(T&& arg) {
3 anotherFunction(std::forward<T>(arg));
4}
这样,无论传递给 forwarder
的是 lvalue 还是 rvalue,都会以相同的方式转发给 anotherFunction
。
万能引用和右值引用的区别是什么
万能引用(也称为转发引用)和纯右值引用在语法上看起来很像,都使用 &&
,但它们在语境和行为上有显著的不同。以下是它们之间的主要区别:
-
定义上的区别:
- 万能引用:只能作为函数模板参数,并且当其类型是以模板参数形式推导出来的时才被认为是万能引用。例如,
template<typename T> void func(T&& arg);
这里的T&&
是一个万能引用。 - 右值引用:在非模板代码或模板代码中,如果引用的类型不是由推导确定的,那么它是一个纯右值引用。例如,
int&& r = 5;
这里的int&&
是一个纯右值引用。
- 万能引用:只能作为函数模板参数,并且当其类型是以模板参数形式推导出来的时才被认为是万能引用。例如,
-
行为上的区别:
- 万能引用:可以绑定到 lvalue 和 rvalue。模板类型推导和引用折叠规则决定了它最终的类型。如果传入的是 lvalue,
T
会被推导为 lvalue 引用,使得T&&
成为 lvalue 引用;如果传入的是 rvalue,T
被推导为非引用类型,使得T&&
保持为 rvalue 引用。 - 右值引用:只能绑定到临时对象或被转换为 rvalue 的对象。它的主要目的是支持移动语义和临时对象的生命周期延长。
- 万能引用:可以绑定到 lvalue 和 rvalue。模板类型推导和引用折叠规则决定了它最终的类型。如果传入的是 lvalue,
-
用途上的区别:
- 万能引用:主要用于"完美转发",即无论传入的是 lvalue 还是 rvalue,都可以将其转发给另一个函数,同时保持原始的值类别。为此,通常与
std::forward
一起使用。 - 右值引用:主要用于实现移动语义,即从一个对象“移动”其资源而不是复制。这在大对象或资源有限的场合(如动态分配的内存)中非常有用,因为移动通常比复制更有效率。
- 万能引用:主要用于"完美转发",即无论传入的是 lvalue 还是 rvalue,都可以将其转发给另一个函数,同时保持原始的值类别。为此,通常与
总的来说,万能引用是模板编程中的一个技巧,允许函数模板根据传入的参数类型动态地选择引用类型(lvalue 引用或 rvalue 引用),而右值引用是 C++11 引入的新型别,用于支持移动语义和对临时对象的高效操作。