news 2026/10/14 3:15:09

思考: C++ 万能引用与完美转发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
思考: C++ 万能引用与完美转发

万能引用、引用折叠与完美转发

总览:一条主线串起所有点

目标:让"传进来的原始参数"尽可能少经过临时对象,直接就地构造到目标里。所有规则都是为这件事服务的。


一、两块基石

1. 类型 ≠ 值类别

一个表达式同时有两个属性:

表达式类型值类别
s1string左值(lvalue)
string("x")string右值(rvalue)

s1(左值)和string("x")(右值)类型都是string,只是值类别不同。

2. 引用折叠(标准规定)

推导结果折叠为
T& &T&
T& &&T&
T&& &T&
T&& &&T&&

只要出现一个&,结果就是左值引用。


二、万能引用为什么要"再写一个函数模板 U"

规则:*X&&是万能引用,当且仅当X是该函数自己、会被推导的模板参数。

写法说明
list<T>::push_back(T&&)T是类模板参数,在list<string>实例化时已定死 → 普通右值引用
list<T>::push_back(U&&)套函数模板U由函数自己推导 → 万能引用

推导 + 折叠效果:

实参左值s1右值string(...)
U推导为string&string
U&&折叠为string&string&&

结论:类模板参数T在list<string>时定死,永远变不成万能引用;必须让函数自己推导U,才能左右值通吃 + 用forward<U>保留值类别。


三、ListNode 两种写法:为什么 1 次 vs 2 次构造

以new Node("literal")为例(字面量类型const char[N]):

写法 A(推导):template<class U> ListNode(U&&)

字面量原样进来(U = const char(&)[N]) → _data(forward<U>(data)) 直接调 string(const char*) ⇒ 1 次构造

写法 B(写死):ListNode(const T&) / ListNode(T&&)

字面量先隐式转成临时 string(进门前) ⇒ 构造 1(转换) → _data(data) / _data(move(data)) 再拷/移 ⇒ 构造 2(拷贝/移动)

根因

写法 B 的形参类型已经是string,字面量必须先在门口变成string临时;写法 A 的形参是"推导出的原始类型",字面量原封不动抵达_data的构造函数。

差异只在参数类型 ≠ 元素类型时出现;若本来传string,两种写法都是 1 次拷/移,打平。


四、push_back vs emplace_back:同一份开销的两个层次

emplace_back= 把insert/ListNode也做成变参转发,让"原始参数"一路直达_data构造:

template<class...Args>voidemplace_back(Args&&...args){insert(end(),std::forward<Args>(args)...);}template<class...Args>iteratorinsert(iterator pos,Args&&...args){...newNode(std::forward<Args>(args)...);...}template<class...Args>ListNode(Args&&...args):_next(nullptr),_prev(nullptr),_data(std::forward<Args>(args)...){}

对比lt.push_back("...")和lt.emplace_back("..."):

调用传字面量"..."传string左值传std::move(s)
push_back门口先转临时string(2 次构造)→ 再拷/移1 次拷贝1 次移动
emplace_back原样直达_data(1 次构造)1 次拷贝1 次移动

层层对应

层次push_back vs emplace_backListNode(const T&) vs ListNode(U&&)
转发层数push_back只转发一层,emplace_back变参转发到底写死类型在门口转换,推导类型原样进内部
效果字面量先变临时对象字面量先变临时对象
优势emplace_back少一次构造推导写法少一次构造

emplace_back的效率优势 = 上面第三节那个"字面量先变临时"的开销,被变参转发消除掉了。


五、一张总表

场景收左值收右值传字面量保留值类别(左右值区分)
T&&(写死,普通右值引用)✗ 只收右值✓✗ 先转临时(2 次构造)部分(左值直接绑不上)
U&&(函数模板,万能引用)✓U=T&✓U=T✓ 原样直达(1 次构造)✓ 配合forward<U>
push_back(一层转发)✓ 拷贝✓ 移动✗ 先转临时✓
emplace_back(变参转发到底)✓ 拷贝✓ 移动✓ 1 次就地构造✓

六、一句话串起来

类模板T在实例化时定死,T&&只是普通右值引用,且"写死的类型"会逼实参在门口先造临时对象;所以要再写一个函数模板U/Args,靠推导 + 引用折叠得到万能引用,再用forward原样把原始参数送到_data的构造函数——这就是ListNode省一次构造、emplace_back比push_back高效的同一套原理。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 3:14:46

Linux 系统调试学习2

1、ps、top、htop 分别干什么&#xff1f;先记这个区别&#xff1a;ps看“当前这一刻”的进程快照top实时刷新系统进程状态htop更友好的交互式 top所以&#xff1a;想快速查一个进程有没有启动 → ps想持续观察 CPU / 内存 → top想更方便地看线程、排序、搜索 → htopps aux 常…

作者头像 李华
网站建设 2026/10/14 3:14:45

AscendNPU-IR 入门指南:基于 MLIR 的昇腾亲和算子编译中间表示

编译器人工智能AscendCANN 【免费下载链接】AscendNPU-IR AscendNPU-IR是基于MLIR&#xff08;Multi-Level Intermediate Representation&#xff09;构建的&#xff0c;面向昇腾亲和算子编译时使用的中间表示&#xff0c;提供昇腾完备表达能力&#xff0c;通过编译优化提升昇腾…

作者头像 李华
网站建设 2026/10/14 3:14:24

Unity Windows原生窗口控制:无边框与状态栏适配实战

1. 项目概述&#xff1a;为什么在Unity里“动”Windows原生窗口是个刚需做Unity桌面应用的开发者&#xff0c;尤其是开发工具类、工业控制面板、数字孪生看板或者需要深度集成Windows系统行为的项目时&#xff0c;迟早会撞上这个墙&#xff1a;Unity默认的窗口管理太“温柔”了…

作者头像 李华
网站建设 2026/10/14 3:13:42

Python爬虫必备:同步与异步HTTP库全解析

在进行数据分析以及竞品调研的时候, 首先需要明确, 最基础也是最关键的一个步骤, 就是必须把数据从网络上面抓取下来。而在整个过程中里最常用的两个 HTTP 库是同步的和异步的这两个&#xff08;具体的库名称可能需要读者自行对应上下文填补空缺&#xff09;, 这两者加在一起就…

作者头像 李华