《C++》【可变参数模板 + emplace接口 + 新的类功能】

发布时间:2026/10/7 10:35:37

《C++》【可变参数模板 + emplace接口 + 新的类功能】 1. 什么是可变参数模板可变参数模板Variadic Templates是 C11 引入的一项强大特性允许模板函数模板或类模板接受数量不确定的参数。这些参数可以是任意类型、任意数量极大地增强了模板的灵活性和表达能力这些参数被称为“参数包”可以分为两大类可变参数模板的核心是“参数包”的分类模板参数包表示零个或多个模板参数类型例如class... Args函数参数包表示零个或多个函数调用参数例如Args... args2. 怎么使用可变参数模板可变参数模板的基本语法与声明形式1. 类模板的可变参数声明代码语言javascriptAI代码解释//可变参数模板类Tuple // 1. 接收任意数量、任意类型的模板参数如Tupleint, string, double // 2. 底层使用 std::tuple 存储数据 template class... Args //注意Args是模板参数包 class Tuple { private: std::tupleArgs... _data; /* 数据成员使用参数包实例化的 std::tuple * * Args... 会被展开为具体类型列表 * 如Tupleint, string 会实例化为 std::tupleint, string */ public: Tuple(Args... args) //注意args是函数参数包 : _data(args...) //初始化列表用参数包 args 初始化 std::tuple { } /* 构造函数接收与模板参数匹配的任意数量实参 * 注意Args... args函数参数包对应模板参数包 Args... 的实例 * * 参数包展开规则 * 若模板实例化为 Tupleint, string则构造函数等价于 * Tuple(int arg1, string arg2) * :_data(arg1, arg2) * {} */ };在这里插入图片描述2. 函数模板的可变参数声明代码语言javascriptAI代码解释/*------------------值传递------------------*/ template class... Args void Func(Args... args) //注意args是函数参数包 { // args 是函数参数包可接收任意类型、任意数量的参数 } /*------------------左值引用------------------*/ template class... Args void Func(Args... args) //注意args是函数参数包 { // 每个参数都是左值引用需绑定到左值如变量 } /*------------------万能引用------------------*/ template class... Args void Func(Args... args) //注意args是函数参数包 { // 每个参数都是万能引用转发引用可绑定左值或右值 }class... Args模板参数包可接收任意数量的类型如int、double、string等Args... args函数参数包可接收与模板参数包类型匹配的任意数量实参Args... args函数参数包的每个参数都是左值引用需绑定到左值如变量Args... args函数参数包的每个参数都是万能引用转发引用可绑定左值/右值结合引用折叠规则适配实参类型3. 参数包的关键特性有哪些1. 编译期类型推导编译器会根据传入的实参自动推导模板参数包的具体类型。代码语言javascriptAI代码解释Func(1, 2.3, hello); // 推导结果 // Args int, double, const char* // args 1 (int), 2.3 (double), hello (const char*)2. 引用折叠规则针对万能引用包对于Args... args形式的万能引用参数包传入左值如变量a→Args推导为int→ 折叠为int左值引用传入右值如10、std::move(a)→Args推导为int→ 折叠为int右值引用4. 参数包可以干什么1. 用 sizeof… 计算参数个数sizeof…(Args) 或 sizeof…(args) 可在编译期计算参数包的元素数量代码语言javascriptAI代码解释#include iostream using namespace std; template class... Args void Func(Args... args) { cout 参数个数 sizeof...(args) endl; } int main() { // 调用 Func(1, 2.3, hello); // 输出参数个数3 return 0; }在这里插入图片描述2. 完美转发std::forward 可变参数包结合std::forward 可变参数包精准转发任意参数代码语言javascriptAI代码解释#include iostream #include string #include utility // 包含 std::move 和 std::forward 的必要头文件 using namespace std; /*-------------------定义Object类-------------------*/ class Object { private: int m_value; //整型成员变量 std::string m_str; //字符串成员变量 Object* m_ptr nullptr; //对象指针成员初始化为nullptr public: //1.构造函数接收多种类型参数 Object(int value, const string str, Object other) : m_value(value) , m_str(str) //注初始化m_str调用string的拷贝构造函数 { //1.处理右值参数转移 other 的资源 //1.1接管other的资源 m_ptr other.m_ptr; //1.2将other的资源置空防止双重释放 other.m_ptr nullptr; cout Object 构造完成 endl; } // 新增构造函数支持用 nullptr 初始化 m_ptr Object(int value, const string str, nullptr_t) : m_value(value) , m_str(str) , m_ptr(nullptr) //注显式将指针成员初始化为 nullptr { cout Object 构造完成nullptr 版本 endl; } //2.析构函数 ~Object() { delete m_ptr; //注意m_str是栈对象会自动调用其析构函数 } }; /*-------------------全局对象指针-------------------*/ Object* obj nullptr; //实际应用中可能是 “类成员变量” /*-------------------可变参数模板函数-------------------*/ template class... Args void Emplace(Args... args) { obj new Object(std::forwardArgs(args)...); //完美转发参数构造 Object 对象 /* 关键逻辑详解 * 1. std::forwardArgs(args)... * - 这是一个参数包展开对每个参数应用std::forward * - 保持参数的原始值类别左值/右值 * - 展开后相当于std::forwardArg1(arg1), std::forwardArg2(arg2), ... * * 2. new Object(...) * - 直接在堆上构造Object对象 * - 避免了创建临时对象再拷贝的开销 * - 完美转发确保调用最匹配的构造函数 */ } int main() { //1.准备参数 Object tempObj(5, temp, nullptr); //2.调用 Emplace参数会按原始属性转发 Emplace(10, hello, move(tempObj)); /* 参数转发分析 * - 10 右值字面量→ 转发为 int * - hello 右值const char[6]转换为临时string→ 转发为 string * - std::move(tempObj)显式转换为右值 → 转发为 Object * * 注意调用后tempObj的资源已被转移不应再使用 */ //3.使用 obj... //4.释放内存 delete obj; // 实际应用中建议使用unique_ptr或shared_ptr管理内存 return 0; }5. 什么是参数包展开对于参数包模板参数包或函数参数包最核心的操作是“展开”。 除了用sizeof...计算参数个数外参数包的主要价值体现在按规则分解为单个元素并对每个元素应用相同逻辑。参数包展开是 C 可变参数模板的核心操作用于将参数包Args...或args...分解为独立的元素并对每个元素应用相同的处理逻辑。 在可变参数模板中参数包是一个未展开的参数集合类似 “黑盒”必须通过特定语法显式展开才能使用。参数包展开的本质是分解参数包将参数包如Args...或args...拆分为独立的类型/值如int, double, string或1, 2.3, hello应用统一模式为每个分解出的元素定义同的处理逻辑如函数调用、运算符操作6. 怎么进行参数包的展开参数包扩展的过程可拆解为分解参数包将参数包拆分为独立的元素。如Args...拆为arg1, arg2, ..., argN应用模式为每个元素定义统一的 “处理模式”。如函数调用、运算符操作 触发扩展通过在模式右侧添加...省略号告知编译器对参数包逐个元素应用模式生成扩展后的代码。参数包扩展的方式递归展开C11 经典方式折叠表达式C17 简化方式6.1递归展开C11 经典方式1递归展开C11 经典方式通过递归调用或逗号表达式可逐个处理参数包的元素C17 后支持更简洁的折叠表达式代码语言javascriptAI代码解释#include iostream using namespace std; /*------------------递归终止函数------------------*/ template class T void Print(T arg) { cout arg endl; //注输出最后一个参数并换行 } /* 注意事项处理最后一个参数仅剩一个参数时调用 * * 1. 模板参数 T捕获最后一个参数的类型 * 2. 参数 arg最后一个实参的值 */ /*------------------递归展开函数------------------*/ template class T, class... Args void Print(T arg, Args... args) { Print(arg); // 1. 处理当前第一个参数递归调用终止函数 Print(args...); // 2. 递归处理剩余参数包展开为 Print(arg1, arg2, ...) } /*------------------主函数------------------*/ int main() { Print(1, 2.3, hello); return 0; } /* 注意事项处理多个参数 * * 1. 模板参数 T 捕获第一个参数的类型 * 2. 模板参数包 Args...捕获剩余参数的类型列表 * * 3. 参数 arg 第一个实参的值 * 4. 参数包 args... 剩余实参的值列表 */ // 调用示例Print(1, 2.3, hello); // 编译器实例化过程 // 1. 初始调用Printint, double, const char*(1, 2.3, hello) // - 匹配多参数版本实例化为 // void Print(int arg, double, const char*) // { // Print(arg); // 调用 Printint(1) // Print(2.3, hello); // 递归调用剩余参数 // } // // 2. 第一次递归Printdouble, const char*(2.3, hello) // - 匹配多参数版本实例化为 // void Print(double arg, const char*) // { // Print(arg); // 调用 Printdouble(2.3) // Print(hello); // 递归调用剩余参数 // } // // 3. 第二次递归Printconst char*(hello) // - 匹配终止函数实例化为 // void Print(const char* arg) // { // cout arg endl; // 输出 hello // }在这里插入图片描述实例化流程以Print(1, 2.3, hello)为例递归层级函数实例化参数传递执行动作1Printint, double, const char*arg1, args2.3, hello输出 1递归调用 Print(2.3, hello)2Printdouble, const char*arg2.3, argshello输出 2.3递归调用 Print(hello)3Printconst char*arghello输出 hello终止递归代码案例 2参数包的递归展开代码语言javascriptAI代码解释/*----------------------------- 通过递归展开参数包实现任意参数打印 -----------------------------*/ #include iostream #include string using namespace std; // 错误示例说明参数包不能直接遍历 // 以下代码演示参数包无法通过运行时循环遍历编译时特性 vs 运行时逻辑 /* * template class... args * void print(args... args) * { * // 错误sizeof...(args) 是编译期常量但 args 不是数组无法用 [] 访问 * cout sizeof...(args) endl; * for (size_t i 0; i sizeof...(args); i) * { * cout args[i] ; // 编译报错参数包不是容器无 [] 运算符 * } * } */ // 递归展开ShowList 系列 //1. 递归终止条件参数包为空时调用匹配无参数的情况 void ShowList() { cout endl; // 换行结束递归打印 } //2. 递归展开函数每次处理参数包的第一个元素剩余参数继续递归 template class T, class... Args void ShowList(T x, Args... args) { //1.打印当前第一个参数 cout x ; //2.递归调用将剩余参数包 args... 继续传递给 ShowList触发下一层展开 ShowList(args...); } // 封装 Print 接口 template class... Args void Print(Args... args) { //1.调用 ShowList 展开参数包实现任意参数打印 ShowList(args...); } // main 函数测试 int main() { // 测试不同参数数量的调用 Print(); // 无参数 → 直接换行 Print(1); // 1个参数 → ShowList(1) → ShowList() Print(1, string(xxxxx)); // 2个参数 → ShowList(1, xxxxx) → ShowList(xxxxx) → ShowList() Print(1, string(xxxxx), 2.2);// 3个参数 → 逐层展开 return 0; } /* // 编译器实例化过程伪代码演示 // 当调用 Print(1, string(xxxxx), 2.2) 时编译器会自动生成以下展开 //1. 最外层 Print 调用 void Print(int, string, double) { ShowList(1, string(xxxxx), 2.2); } //2. 第一次 ShowList 展开处理 int void ShowList(int x, string, double) { cout x ; // 输出: 1 ShowList(string(xxxxx), 2.2); } //3. 第二次 ShowList 展开处理 string void ShowList(string x, double) { cout x ; // 输出: xxxxx ShowList(2.2); } //4. 第三次 ShowList 展开处理 double void ShowList(double x) { cout x ; // 输出: 2.2 ShowList(); } //5. 递归终止 void ShowList() { cout endl; // 输出换行 } */在这里插入图片描述在这里插入图片描述6.2折叠表达式C17 简化方式2折叠表达式C17 简化方式利用…折叠表达式无需递归即可展开参数包代码语言javascriptAI代码解释#include iostream #include string using namespace std; /* 可变参数模板函数使用折叠表达式展开参数包 * * 1. Args...模板参数包接收任意数量、任意类型的参数 * 2. args...函数参数包对应模板参数包的实例 */ template class... Args void Print(Args... args) { (cout ... args) endl; /* 折叠表达式展开参数包并通过 运算符连接 * 展开过程(cout arg1 arg2 ... argN) endl; * * 注意需要确保所有参数类型都支持 运算符如内置类型、string 等 */ } int main() { /*----------------混合类型参数----------------*/ Print(1, , 2.3); // 输出1 2.3 /* 调用示例参数类型包括 int、const char*、double * * 展开结果(cout 1 2.3) endl; */ string name Alice; Print(Name:, name, , Age:, 20); // 输出Name: Alice, Age: 20 /*----------------空参数调用----------------*/ Print(); // 输出空行 return 0; }/---------- 折叠表达式简单介绍----------/1. 折叠表达式语法折叠表达式是 C17 的特性用于简化可变参数模板的展开一元右折叠代码语言javascriptAI代码解释(expr1 ... exprN)展开顺序从左到右等价于((expr1 expr2) ... exprN)一元左折叠代码语言javascriptAI代码解释(... expr)展开顺序从右到左等价于(expr1 (expr2 ... exprN))2. 参数包展开过程以Print(1, , 2.3)为例模板参数包Args推导为int, const char*, double函数参数包args对应为1, , 2.3折叠表达式代码语言javascriptAI代码解释(cout ... args)展开为代码语言javascriptAI代码解释((cout 1) ) 2.3最终输出1 2.33. 类型要求折叠表达式要求所有参数类型都支持 运算符内置类型如int、double、char*标准库类型如std::string、std::vector等需重载自定义类型需显式重载运算符4. 与递归展开的对比与传统递归展开方式C11/14相比折叠表达式C11的优势代码更简洁— 无需手动编写递归终止函数展开效率更高— 编译期一次性展开而非递归调用
延伸阅读

更多相关文章

2026/10/6 3:30:22

彻底解决C++编译报错:从C++98升级到现代C++标准的完整指南

1. 从一次典型的编译报错说起 那天下午,我正在重构一个老项目的日志模块,打算引入C11的 std::unique_ptr 来管理一个文件句柄资源,避免手动 fclose 可能带来的内存泄漏。代码写起来很顺畅, auto 关键字让迭代器声明变得简洁…

2026/10/6 23:05:16

Claude Code与MCP实战:打造能自主规划任务的AI编程智能体

如果你是一名开发者,最近可能已经感受到了 AI 编程助手带来的效率冲击。从 Copilot 的代码补全,到 Cursor 的对话式编程,再到 Devin 引发的“AI 程序员”热议,工具迭代的速度远超想象。但你是否也遇到过这样的困境:助手…

2026/10/7 10:30:42

零依赖PHP短链服务实战:原生代码+SQLite打造极简URL缩短器

最近 caveman 这个词在热搜上露脸的时候,我还愣了一下——这不就是我折腾过好几次的那套极简思维吗?项目本身不大,但背后的取舍过程,倒是很值得拆开来讲讲。这里说的 caveman,是我给自己的一个 URL 短链服务起的名字。…

2026/10/7 10:30:42

游戏引擎基础架构拆解:分层、核心模块与ECS

实话说,搞游戏开发的朋友,不管你是写玩法逻辑还是做引擎工具,迟早都会碰到一个绕不开的话题:游戏引擎架构。尤其是当你从“用引擎”切换到“看引擎”“改引擎”甚至“写引擎”的时候,最直观的感受就是——这玩意儿怎么…

2026/10/7 10:30:42

深入拆解RoCE网络:PFC、ECN与DCQCN机制与调优

最近半年我接了不少RoCE网络的排障和优化需求,有做存储的、有跑AI训练的,从100G到400G都有。说实话,大部分问题的根源不是网卡也不是交换机硬件性能,而是大家对RoCE这套机制“七分懂三分不懂”:知道要开PFC&#xff0c…

2026/10/7 10:30:42

JSP+Servlet+JDBC学生信息管理系统:分页搜索与CRUD实战

简介:面向Java Web课程设计与期末作业的学生信息管理系统项目包,涵盖登录验证、学生信息增删改查、课程关联等典型功能,适合正在学习Servlet/JSP、MVC分层架构与数据库编程的初学者。压缩包共209个文件,大小仅4.57MB,包…

2026/10/7 10:30:42

Android电子书阅读系统实战:SpringBoot后端与移动端全栈开发解析

1. 项目概述 接手这个基于Android的电子书阅读系统时,我的第一反应是:这又是一个典型的Java全栈毕业设计项目,后端SpringBoot负责业务逻辑,前端Android负责展示交互,再加一套完整的源码文档视频配套。但真正动手做完之…

2026/10/7 10:25:42

C#.NET大文件夹上传方案:分片上传与断点续传实战

直接切入正题。我接手过一个内部系统,需求描述很简短:“用户要把整个项目目录拖进网页里传上去,服务器上要能看到一模一样的文件夹结构。” 听起来跟平时传几个文件没啥区别,但做过上传功能的人都知道,这句话背后全是坑…

2026/10/5 6:32:56

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/7 8:18:33

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/6 17:46:51

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

ESP32免重刷固件:浏览器直接修改NVS键值实现WiFi配置更新

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

SAP HANA查询结果导出CSV:避开乱码、性能与权限的实用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

数字后端Placement阶段Density与Congestion控制实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑