C++中的万能引用是模板参数推导过程中出现的一种特殊引用类型,它既可以绑定左值也可以绑定右值,很多开发者也会把它称为转发引用,二者指的是同一个概念。这种引用类型只在模板参数推导的特定场景下出现,和普通的右值引用有明显区别。

万能引用的定义与判断
万能引用的形式看起来和右值引用类似,都是T&&,但二者的出现场景完全不同。普通的右值引用只能绑定右值,而万能引用可以绑定任意值类别的对象。
判断一个T&&是不是万能引用,核心看T是否是通过模板参数推导得到的:
- 如果是函数模板的参数,且
T是模板参数,那么T&&是万能引用 - 如果
T是已经确定的具体类型,比如int&&,那么就是普通的右值引用
示例代码对比
#include <iostream>
#include <type_traits>
// 模板函数,T是模板参数,param是万能引用
template <typename T>
void func1(T&& param) {
std::cout << "param is lvalue ref: " << std::is_lvalue_reference<T&&>::value << std::endl;
std::cout << "param is rvalue ref: " << std::is_rvalue_reference<T&&>::value << std::endl;
}
// 普通函数,参数是确定的右值引用
void func2(int&& param) {
std::cout << "func2 param is rvalue ref" << std::endl;
}
int main() {
int a = 10;
func1(a); // 传入左值,T会被推导为int&,param是左值引用
func1(20); // 传入右值,T会被推导为int,param是右值引用
// func2(a); // 编译错误,右值引用不能绑定左值
func2(20); // 正确,右值引用绑定右值
return 0;
}
引用折叠规则
万能引用之所以能绑定任意值类别,本质是C++的引用折叠规则在起作用。当模板参数T被推导为引用类型时,T&&会按照以下规则折叠:
| 推导后的T类型 | T&&的实际类型 |
|---|---|
| T(非引用) | T&&(右值引用) |
| T&(左值引用) | T&(左值引用) |
| T&&(右值引用) | T&&(右值引用) |
简单来说,只要出现左值引用,最终折叠结果就是左值引用,只有所有引用都是右值引用时,结果才是右值引用。
完美转发的实现
完美转发指的是在函数模板中,将参数按照原来的值类别(左值/右值)传递给另一个函数,不丢失参数的原始属性。要实现完美转发,需要结合万能引用和std::forward函数。
std::forward被称为条件转发函数,它只有在参数是右值引用时才会把参数转换为右值,否则保持左值属性。
完美转发示例
#include <iostream>
#include <utility>
// 目标函数,区分左值和右值参数
void target_func(int& val) {
std::cout << "target_func called with lvalue: " << val << std::endl;
}
void target_func(int&& val) {
std::cout << "target_func called with rvalue: " << val << std::endl;
}
// 转发函数,使用万能引用和std::forward实现完美转发
template <typename T>
void forward_func(T&& param) {
// 将param完美转发给target_func
target_func(std::forward<T>(param));
}
int main() {
int a = 10;
forward_func(a); // 传入左值,转发后调用左值版本target_func
forward_func(20); // 传入右值,转发后调用右值版本target_func
return 0;
}
常见使用场景
万能引用和完美转发最常见的使用场景是工厂函数和容器插入操作:
- 工厂函数需要接收任意类型的构造参数,然后完美转发给对象的构造函数,避免不必要的拷贝
- 容器如
std::vector的emplace_back方法就使用了完美转发,直接在容器内构造对象,比push_back效率更高
工厂函数示例
#include <iostream>
#include <memory>
#include <utility>
class MyClass {
public:
MyClass(int val, const std::string& name) : val(val), name(name) {
std::cout << "MyClass constructed, val: " << val << ", name: " << name << std::endl;
}
private:
int val;
std::string name;
};
// 完美转发的工厂函数
template <typename... Args>
std::unique_ptr<MyClass> create_myclass(Args&&... args) {
return std::make_unique<MyClass>(std::forward<Args>(args)...);
}
int main() {
auto obj1 = create_myclass(10, "test1"); // 转发右值参数
std::string name = "test2";
auto obj2 = create_myclass(20, name); // 转发左值参数,不会拷贝字符串
return 0;
}
注意事项
使用万能引用和完美转发时需要注意几个问题:
- 不要对万能引用使用
std::move,否则会导致左值被错误转换为右值,引发意外的问题 std::forward需要显式指定模板参数T,否则无法正确触发引用折叠- 只有模板参数推导场景下的
T&&才是万能引用,普通函数重载中的T&&不是
需要注意的是,万能引用和转发引用是同一个概念的不同称呼,最早由Scott Meyers提出万能引用的说法,后来标准委员会官方命名为转发引用,二者指代的内容完全一致。