万能引用、完美转发

注意:

C++复习笔记,尽量结合上一篇左值右值的文章一起复习:左值右值

1. 什么是"万能引用"?

现象

通常我们认为 && 代表右值引用,但在一种特殊情况下,它既能绑定左值,也能绑定右值。

触发条件

必须同时满足两个条件:

  1. 类型推导 :变量的类型必须是推导出来的(比如模板参数 T,或者 auto)。
  2. 格式固定 :必须严格写成 T&& 的形式。
对比示例

C++

复制代码
void func1(int&& x) {} 
// ❌ 没有类型推导。
// x 确定就是右值引用,只能传右值(比如 10),传左值会报错。

template<typename T>
void func2(std::vector<T>&& x) {}
// ❌ 格式不对。虽然有 T,但引用是绑在 vector 上的,不是直接绑在 T 上。
// 这依然是右值引用。

template<typename T>
void func3(T&& x) {}
// ✅ 满足条件!T 是推导的,且格式是 T&&。
// 这就是【万能引用】。

万能引用的能力:

  • 如果你传给它一个左值 ,它就变成左值引用。
  • 如果你传给它一个右值 ,它就变成右值引用。

2. 全过程推演:编译器在后台做了什么?

让我们看一个万能引用的函数,看看当我们分别传入左值和右值时,编译器是如何利用引用折叠来推导类型的。

复制代码
template<typename T>
void wrapper(T&& param) { 
    // ... 
}
情况 A:传入左值
复制代码
int a = 10;
wrapper(a); // 传入左值
  1. 推导 T :因为 a 是左值,编译器把 T 推导为 int&(注意:T 包含了引用)。
  2. 代入 T&& :将 int& 代入 wrapper(T&& param)。
  3. 产生代码 :void wrapper(int& && param)。
  4. 引用折叠 :遇到 & 和 &&,根据规则(逢左必左),折叠为 &。
  5. 最终签名 :void wrapper(int& param)。
    • 结果 :param 变成了左值引用,正确接收了左值 a。
情况 B:传入右值

C++

复制代码
wrapper(10); // 传入右值
  1. 推导 T :因为 10 是右值,编译器把 T 推导为 int(注意:T 不包含引用)。
  2. 代入 T&& :将 int 代入 wrapper(T&& param)。
  3. 产生代码 :void wrapper(int && param)。
  4. 引用折叠:没有双重引用,不需要折叠。
  5. 最终签名 :void wrapper(int&& param)。
    • 结果 :param 变成了右值引用,正确接收了右值 10。

4.完美转发 (Perfect Forwarding)

假设你想写一个工厂函数 factory,它接收参数,然后把这些参数原封不动地传给某个类的构造函数。

  • 如果参数是左值,传给构造函数时也得是左值。
  • 如果参数是右值(临时对象),传给构造函数时也得是右值(以便触发移动构造)。

如果没有万能引用,你很难做到这一点(可能需要写无数个重载版本)。有了万能引用和引用折叠,配合 std::forward,就能实现完美转发。

复制代码
void actualFunc(int& x)  { std::cout << "左值 process" << std::endl; }
void actualFunc(int&& x) { std::cout << "右值 process" << std::endl; }

template<typename T>
void middleware(T&& arg) {
    // 这里的 arg 虽然也是引用,但作为变量本身,它在函数内部是一个左值(左值退化)
    // 如果直接传 actualFunc(arg),永远会调用左值版本。

    // std::forward<T>(arg) 会根据 T 的类型(包含折叠信息)
    // 决定是把 arg 转成左值引用还是右值引用。
    actualFunc(std::forward<T>(arg));
}

int main() {
    int a = 10;
    middleware(a);  // 输出: 左值 process
    middleware(10); // 输出: 右值 process
}

总结:

  1. 万能引用 ( T&&****):像一个变色龙,利用模板推导,来者不拒,既接左值也接右值。
  2. 引用折叠:是编译器处理类型推导时解决"引用之引用"冲突的数学规则(逢左必左)。
  3. 完美转发 :利用上述两个机制,配合 std::forward,让中间层函数完全保留参数最原始的类型属性(左/右值、const/non-const)。
相关推荐
水云桐程序员1 小时前
从零开发软件的步骤,软件开发从零到完成应该怎么做?
笔记·学习·软件构建·个人开发
David猪大卫1 小时前
【Linux修炼】环境变量、进程地址空间
linux·c语言·经验分享·笔记·学习·ubuntu·centos
叼烟扛炮2 小时前
Linux 学习笔记(五十六):IO 多路复用初探——select 的“三宗罪”
linux·服务器·数据库·笔记·学习·php
汉克老师3 小时前
CSP-J 2026 第二轮入门组真题解析(第一题 、旅游)
c++·csp-j·信息学竞赛·小学生学c++
xiaoye-duck3 小时前
《C++ 项目:手写大模型接入 SDK》Gemini接口设计(上):初始化、全量响应实现和测试
开发语言·网络·c++·语言模型
UIU1147 小时前
P1481 魔族密码
c++·算法·动态规划
叼烟扛炮9 小时前
Linux 学习笔记(五十八):IO 多路复用封神——epoll 底层原理揭秘
linux·运维·服务器·网络·笔记·学习·php
陈布衣10 小时前
MySQL(1)_MySQL 5.7.21 Linux 安装笔记
linux·笔记·mysql
闻缺陷则喜何志丹13 小时前
【C++特性】四种类型转
c++·算法·类型转换
缘来是你远去是我13 小时前
本地个人笔记exe
笔记