2.万能引用和完美转发
一、万能引用
1. 万能引用(Universal Reference)
定义:万能引用不是一种新的引用类型,而是 C++11 中对特定形式的引用的称呼 —— 只有当模板参数是 T&& 且 T 是被推导的模板参数时,这个引用才是万能引用(也叫转发引用)。
关键特征:
- 形式必须是
T&&(模板参数的右值引用形式) - 必须发生类型推导(比如模板函数的参数)
- 能绑定到左值、右值、const/non-const 等所有类型
反例(不是万能引用):
1 | // 1. 没有模板参数推导,只是普通右值引用 |
正例(万能引用):
1 | template <typename T> |
2. 引用折叠规则(理解万能引用的核心)
万能引用能适配所有类型,本质是靠引用折叠规则:
| 左操作数 | 右操作数 | 结果 |
|---|---|---|
| T& | & | T& |
| T& | && | T& |
| T&& | & | T& |
| T&& | && | T&& |
简单记:只要有一个左值引用,结果就是左值引用;只有两个都是右值引用,结果才是右值引用。
示例:
1 | template <typename T> |
辅助理解的代码示例
1 |
|
输出结果
1 | T is lvalue reference: true |
核心判断工具( 头文件)
| 表达式 | 作用 |
|---|---|
std::is_lvalue_reference<T>::value |
判断模板推导的类型 T 是否为左值引用 |
std::is_rvalue_reference<T>::value |
判断模板推导的类型 T 是否为右值引用 |
std::is_rvalue_reference<T&&>::value |
判断 T&& 最终折叠后的类型是否为右值引用 |
std::is_lvalue_reference<decltype(x)>::value |
判断参数 x 的实际类型是否为左值引用 |
std::is_rvalue_reference<decltype(x)>::value |
判断参数 x 的实际类型是否为右值引用 |
关键易错点
- 右值引用变量(如
int&& c = 20;)本身是左值(有名字、可取地址); std::move(变量)可将左值(包括右值引用变量)强制转为右值引用。
四次调用的详细分析
1. 调用 1:check_reference(a)(传入左值 a)
模板推导:
T = int&(左值引用);引用折叠:
T&& = int& && → int&(左值引用);输出结果:
1
2
3
4
5T is lvalue reference: true
T is rvalue reference: false
T is rvalue: false
x is lvalue reference: true
x is rvalue reference: false
2. 调用 2:check_reference(20)(传入右值 20)
模板推导:
T = int(非引用);引用折叠:
T&& = int&&(右值引用);输出结果:
1
2
3
4
5T is lvalue reference: false
T is rvalue reference: false
T is rvalue: true
x is lvalue reference: false
x is rvalue reference: true
3. 调用 3:check_reference(c)(传入右值引用变量 c)
核心:
c是右值引用类型,但变量本身是左值;模板推导:
T = int&(左值引用);引用折叠:
T&& = int& && → int&(左值引用);输出结果(同调用 1):
1
2
3
4
5T is lvalue reference: true
T is rvalue reference: false
T is rvalue: false
x is lvalue reference: true
x is rvalue reference: false
4. 调用 4:check_reference(std::move(c))(传入 std::move(c))
核心:
std::move(c)将左值c转为右值引用;模板推导:
T = int(非引用);引用折叠:
T&& = int&&(右值引用);输出结果(同调用 2):
1
2
3
4
5T is lvalue reference: false
T is rvalue reference: false
T is rvalue: true
x is lvalue reference: false
x is rvalue reference: true
核心结论
- 万能引用
T&&的实际类型由传入参数的值类别决定,左值入 → 左值引用出,纯右值入 → 右值引用出; - 右值引用变量(如
c)本质是左值,需通过std::move才能还原为右值引用; decltype(x)反映参数x的实际引用类型,而T的类型是模板推导的原始结果,需结合引用折叠才能确定最终类型。
二、完美转发
1. 为什么需要完美转发?
完美转发的核心诉求是 ——让函数模板在转发参数时,完全保留参数原本的「值类别」(左值 / 右值)和「常量属性」(const/non-const)。
先看一个反例,没有完美转发时会出什么问题:
1 |
|
输出结果:
1 | 接收左值: 10 |
可以看到:哪怕传入的是右值 10,但在 wrapper 里 x 是具名变量(左值),导致转发给 func 时丢失了原有的右值属性 —— 这就是完美转发要解决的问题。
二、核心实现:std::forward 怎么用?
std::forward 是 C++11 提供的「转发工具」,定义在 <utility> 头文件中,它的作用是:根据模板参数的推导结果,把参数还原为原本的左值 / 右值。
1. 修复后的代码(完美转发)
1 |
|
输出结果:
1 | 接收左值: 10 |
2. std::forward 的底层逻辑(简化版)
std::forward 的实现依赖「引用折叠」和「条件类型转换」,简化后的核心逻辑如下:
1 | // 针对左值引用(T是T&):返回左值引用 |
简单说:
- 当
T推导为int&(传入左值):std::forward<T>(x)→ 转换为int&(左值); - 当
T推导为int(传入右值):std::forward<T>(x)→ 转换为int&&(右值)。