[C++11] 变长参数模板

说明:C++11 引入了可变参数模板(Variadic Templates)这一功能,也称为变长模板参数。它允许创建可以接受任意数量参数的模板函数或类。使用可变参数模板的基本语法如下:

template <typename... Args>
void function(Args... args) {// 在这里处理 args 参数
}

其中:

  • typename... Args 表示模板参数包(parameter pack)。这个占位符可以接受任意数量、任意类型的参数。
  • Args... args 表示函数参数包。在函数内部,可以使用 args... 来访问这些参数。

接下来使用C++11新特性中的可变参数模板实现一个简单的 printf 函数(便于理解)来测试和验证,代码如下所示:

#include <iostream>
#include <cstdio>
#include <string>// 基本版本,处理单个参数
template <typename T>
void printf(const char* format, T value) {std::printf(format, value);
}// 可变参数版本,递归处理剩余参数
template <typename T, typename... Args>
void printf(const char* format, T value, Args... args) {// 首先处理当前参数std::size_t len = std::strlen(format);char* temp = new char[len + 1];std::strcpy(temp, format);//在临时字符串中查找第一个 % 字符的位置。char* ptr = std::strchr(temp, '%');if (ptr) {//将 % 字符替换为空字符,将字符串分隔为两部分。*ptr = '\0';//使用 std::printf 输出前一部分字符串,并用 value 参数替换 % 占位符。std::printf(temp, value);//递归调用 printf 函数,处理剩余的格式字符串和参数。printf(ptr + 1, args...);} else {//直接输出整个临时字符串。std::printf(temp);//递归调用 printf 函数,处理剩余的格式字符串和参数。printf(format, args...);}delete[] temp;
}int main() {printf("Hello, %s! Your age is %d.\n", "Alice", 25);printf("The value is %f.\n", 3.14);printf("This is a message.\n");return 0;
}

这个实现包含两个重载版本的 printf 函数:

  • 基本版本处理单个参数,直接调用 std::printf 进行输出。
  • 可变参数版本递归处理剩余参数。它首先解析格式字符串,找到第一个 % 占位符,输出前面的字符串,然后递归处理剩余参数。

在 main 函数中,我们可以看到如何使用这个 printf 函数:

  • 第一个调用包含两个参数,分别为字符串和整数。
  • 第二个调用包含一个浮点数参数。
  • 第三个调用没有任何参数。

这个示例展示了可变参数模板的强大功能,可以用来实现类似 printf 这样的可变参数函数。通过递归调用的方式,可以处理任意数量和类型的参数。

1 为什么C++11新特性中要引入变长参数模板?

这里我们将这个问题拆分成2个问题:引入变长参数模板带来了什么好处 以及 为什么C++11之前未引入该特性,C++11之后才引入来解读

1.1 引入变长参数模板带来了什么好处?

C++11 引入可变参数模板(Variadic Templates)的主要目的有以下几点:

  • 提高代码的灵活性和表达能力:可变参数模板允许创建可以接受任意数量和类型参数的函数或类。这极大地提高了代码的灵活性,使得开发人员能够更好地适应不同的需求。相比之前需要编写大量重载版本的情况,可变参数模板提供了一种更优雅、更简洁的解决方案。
  • 支持函数重载和泛型编程:可变参数模板可以与函数重载一起使用,实现更强大的泛型编程能力。开发者可以编写一组通用的函数或类,适用于各种不同的参数类型和数量,而无需为每种情况编写单独的实现。
  • 简化常见编程模式的实现:许多常见的编程模式,如 printf、tuple、pack 展开等,都可以使用可变参数模板更简单、更优雅地实现。这不仅提高了代码的可读性和可维护性,也减少了重复劳动。
  • 促进元编程和泛型编程的发展:可变参数模板为元编程和泛型编程提供了强大的基础设施。开发者可以利用可变参数模板编写更复杂的元函数和元类,实现更具表现力和抽象能力的编程范式。

总之,C++11 引入可变参数模板是为了增强 C++ 语言的表达能力和灵活性,提高代码的通用性和可重用性,并为元编程和泛型编程的发展奠定基础。这一特性极大地丰富了 C++ 的编程范式,使得开发人员能够编写更加简洁、高效和易维护的代码。

1.2 为什么C++11之前未引入该特性,C++11之后才引入?

在 C++11 之前,C++ 语言没有引入可变参数模板的原因主要有以下几点:

  • 语言复杂性和可维护性:在 C++03 及更早的版本中,引入可变参数模板会增加语言的复杂性和学习成本。当时的编译器实现可能也不太成熟,可能会导致性能问题或者编译错误。
  • 向后兼容性:可变参数模板的引入会破坏一些现有的代码,因为之前的函数重载规则会发生变化。这可能会导致一些已有的代码无法编译通过,给使用者带来不便。
  • 标准化和实现难度:在 C++03 时代,设计一个合理的可变参数模板机制并将其标准化还存在一些技术上的挑战。编译器厂商也需要花费大量精力来实现这一特性,这可能会延缓语言标准的发展。
  • 使用场景有限:在 C++03 时代,许多开发人员并没有强烈的需求来使用可变参数模板。大多数人仍然可以通过其他方式,如函数重载、可变参数宏等手段来满足需求。

随着时间的推移,C++ 语言的发展和编译器技术的进步,这些限制逐渐被克服,具体如下:

  • C++11 的引入极大地提高了语言的表达能力和抽象能力,使得可变参数模板成为了一个自然而然的补充。
  • 编译器的实现也变得更加成熟和优化,可以很好地支持可变参数模板,而不会带来性能问题。
  • 标准化委员会经过深思熟虑,设计出了一套合理的可变参数模板机制,并得到了广泛的认可。
  • 随着泛型编程和元编程技术的发展,可变参数模板的使用场景变得越来越广泛和重要。

总之,在 C++11 引入可变参数模板之前,语言的发展阶段、编译器技术的成熟程度以及标准化过程中的权衡考虑都是导致其缺失的主要原因。但随着 C++ 语言的不断进化,这一特性最终还是在 C++11 中被引入,大大增强了C++ 的表达能力和抽象能力。

2 变长参数模板 使用详解

2.1 实现日志记录系统

参考代码实现如下:

#include <iostream>
#include <sstream>
#include <string>enum class LogLevel { DEBUG, INFO, WARN, ERROR };template <LogLevel level, typename... Args>
void log(const std::string& message, const Args&... args) {std::ostringstream oss;oss << "[" << level << "] " << message << "\n";print(std::cout, oss.str(), args...);
}int main() {log<LogLevel::DEBUG>("Debugging information: x = %d, y = %f", 42, 3.14);log<LogLevel::INFO>("Information message: user logged in");log<LogLevel::WARN>("Warning: disk space is running low");log<LogLevel::ERROR>("Error: failed to open file '%s'", "example.txt");return 0;
}

在这个例子中,我们使用可变参数模板实现了一个支持不同日志级别的日志记录系统。通过模板参数 LogLevel,我们可以控制日志的输出格式和级别。

2.2 实现通用的事件/回调系统

参考代码实现如下:

#include <functional>
#include <vector>template <typename... Args>
class EventHandler {
public:using CallbackType = std::function<void(Args...)>;void addCallback(const CallbackType& callback) {callbacks.push_back(callback);}void trigger(Args... args) {for (const auto& callback : callbacks) {callback(args...);}}private:std::vector<CallbackType> callbacks;
};int main() {EventHandler<int, std::string> myEvent;myEvent.addCallback([](int x, const std::string& s) {std::cout << "Event triggered with: " << x << ", " << s << "\n";});myEvent.trigger(42, "Hello, world!");return 0;
}

这个例子展示了如何使用可变参数模板实现一个通用的事件/回调系统。通过模板参数指定事件的参数类型,我们可以创建不同类型的事件处理器,并注册/触发相应的回调函数。

2.3 实现通用的序列化和反序列化功能

参考代码实现如下:

#include <iostream>
#include <sstream>
#include <tuple>
#include <type_traits>template <typename... Args>
std::string serialize(const Args&... args) {std::ostringstream oss;((oss << args << " "), ...);return oss.str();
}template <typename... Args>
std::tuple<Args...> deserialize(const std::string& data) {std::istringstream iss(data);return std::make_tuple(([&]() {typename std::decay_t<Args> arg;iss >> arg;return arg;})...);
}int main() {auto data = serialize(42, 3.14, "hello");std::cout << "Serialized data: " << data << std::endl;auto tuple = deserialize<int, double, std::string>(data);std::cout << "Deserialized data: " << std::get<0>(tuple) << ", " << std::get<1>(tuple) << ", " << std::get<2>(tuple) << std::endl;return 0;
}

在这个例子中,我们使用可变参数模板实现了通用的序列化和反序列化功能。serialize 函数可以接受任意数量和类型的参数,并将它们序列化为一个字符串。deserialize 函数则可以将序列化后的字符串反序列化为一个 std::tuple。这种通用的序列化和反序列化方式在很多应用场景中都非常实用。

2.4 实现通用的单元测试框架

参考代码实现如下:

#include <iostream>
#include <sstream>
#include <string>
#include <vector>template <typename... Args>
void assertEqual(const std::string& message, const Args&... expected, const Args&... actual) {std::ostringstream oss;oss << "Test failed: " << message << ". Expected: [";((oss << expected << ", "), ...);oss << "], Actual: [";((oss << actual << ", "), ...);oss << "]";if ((expected == actual) && ...) {std::cout << "Test passed: " << message << std::endl;} else {throw std::runtime_error(oss.str());}
}int main() {try {assertEqual("Check integer values", 42, 42);assertEqual("Check floating-point values", 3.14, 3.14);assertEqual("Check string values", "hello", "hello");assertEqual("Check multiple values", 1, 2.0, "three", 1, 2.0, "three");} catch (const std::exception& e) {std::cerr << "Error: " << e.what() << std::endl;return 1;}return 0;
}

在这个例子中,我们使用可变参数模板实现了一个简单的单元测试框架。assertEqual 函数可以接受任意数量和类型的预期值和实际值,并比较它们是否相等。如果不相等,则抛出异常并输出详细的错误信息。这种通用的断言函数可以大大简化单元测试的编写过程。

2.5 实现通用的命令行参数解析器

参考代码实现如下:

#include <iostream>
#include <map>
#include <optional>
#include <string>
#include <tuple>template <typename T>
std::optional<T> convert(const std::string& str) {T value;std::istringstream iss(str);if (iss >> value) {return value;}return std::nullopt;
}template <typename... Args>
std::map<std::string, std::tuple<std::optional<Args>...>> parseArgs(int argc, char* argv[]) {std::map<std::string, std::tuple<std::optional<Args>...>> result;for (int i = 1; i < argc; i += 2) {std::string key = argv[i];std::tuple<std::optional<Args>...> values;for (int j = 0; j < sizeof...(Args); j++) {std::optional<typename std::tuple_element<j, std::tuple<Args...>>::type> value = convert<typename std::tuple_element<j, std::tuple<Args...>>::type>(argv[i + 1 + j]);std::get<j>(values) = value;}result.emplace(key, values);}return result;
}int main(int argc, char* argv[]) {auto args = parseArgs<int, double, std::string>(argc, argv);for (const auto& [key, values] : args) {std::cout << "Key: " << key << ", Values: ";if (std::get<0>(values)) {std::cout << std::get<0>(values).value();} else {std::cout << "none";}std::cout << ", ";if (std::get<1>(values)) {std::cout << std::get<1>(values).value();} else {std::cout << "none";}std::cout << ", ";if (std::get<2>(values)) {std::cout << std::get<2>(values).value();} else {std::cout << "none";}std::cout << std::endl;}return 0;
}

在这个例子中,我们使用可变参数模板实现了一个通用的命令行参数解析器。parseArgs 函数可以接受任意数量和类型的参数,并将它们解析为一个 std::map。每个参数都以键值对的形式存储,并使用 std::optional 来处理可能缺失的值。这种通用的参数解析方式在许多命令行工具和应用程序中都非常实用。

2.6 实现通用的线程池

参考代码实现如下:

#include <functional>
#include <future>
#include <queue>
#include <thread>
#include <vector>template <typename... Args>
class ThreadPool {
public:ThreadPool(size_t numThreads) {for (size_t i = 0; i < numThreads; i++) {threads.emplace_back([this]() {while (true) {std::function<void(Args...)> task;{std::unique_lock<std::mutex> lock(mutex);if (tasks.empty()) {condition.wait(lock);}task = std::move(tasks.front());tasks.pop();}task(std::forward<Args>(args)...);}});}}~ThreadPool() {{std::unique_lock<std::mutex> lock(mutex);stop = true;}condition.notify_all();for (auto& thread : threads) {thread.join();}}template <typename Func, typename... FuncArgs>auto enqueue(Func&& func, FuncArgs&&... args) -> std::future<std::invoke_result_t<Func, Args...>> {auto task = std::bind(std::forward<Func>(func), std::forward<Args>(args)...);auto result = std::make_shared<std::promise<std::invoke_result_t<Func, Args...>>>();{std::unique_lock<std::mutex> lock(mutex);if (stop) {throw std::runtime_error("ThreadPool has been stopped");}tasks.emplace([task, result]() {try {result->set_value(task(std::forward<Args>(args)...));} catch (...) {result->set_exception(std::current_exception());}});}condition.notify_one();return result->get_future();}private:std::vector<std::thread> threads;std::queue<std::function<void(Args...)>> tasks;std::mutex mutex;std::condition_variable condition;bool stop = false;
};int main() {ThreadPool<int, std::string> pool(4);auto future1 = pool.enqueue([](int x, const std::string& s) {std::cout << "Task 1 executed with: " << x << ", " << s << std::endl;return x * 2;}, 42, "hello");auto future2 = pool.enqueue([](int x, const std::string& s) {std::cout << "Task 2 executed with: " << x << ", " << s << std::endl;return x / 2;}, 24, "world");std::cout << "Result 1: " << future1.get() << std::endl;std::cout << "Result 2: " << future2.get() << std::endl;return 0;
}

在这个例子中,我们使用可变参数模板实现了一个通用的线程池。ThreadPool 类可以处理任意数量和类型的参数,并将它们传递给工作线程执行。通过使用 std::future 和 std::promise,我们可以异步地执行任务并获取结果。这种通用的线程池实现在需要并发处理不同类型任务的应用程序中非常有用。

这些demo进一步展示了可变参数模板在 C++ 开发中的广泛应用。无论是基础库的实现还是应用层的开发,可变参数模板都可以发挥重要作用,提高代码的灵活性和可扩展性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/400712.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

<Qt> 系统 - 网络编程 | 音视频

目录 前言&#xff1a; 一、QUdpSocket &#xff08;一&#xff09;核心 API 概览 &#xff08;二&#xff09;设计一个UDP回显服务器 二、QTCPSocket &#xff08;一&#xff09;核心 API 概览 &#xff08;二&#xff09;设计一个TCP回显服务器 三、HTTP Client 四、…

msgqueue.hpp队列模块

目录 一.MsgQueue模块介绍 二.MsgQueue类的实现 成员变量 构造函数与析构函数 成员函数 参数设置函数 setArgs 参数获取函数 getArgs 三.MsgQueueMapper类的实现 成员变量 构造函数 成员函数 创建表格函数 createTable 删除表格函数 dropTable 插入数据函数 inse…

GPT-4o:开启多模态AI识别新纪元

GPT-4o功能简介 在人工智能的演变历程中&#xff0c;图像识别技术始终占据着核心地位。技术的发展日新月异&#xff0c;使得AI不仅能够识别图像内容&#xff0c;还能将其转化为文字描述。特别值得一提的是&#xff0c;OpenAI在春季发布的GPT-4o模型&#xff0c;将图像识别技术…

微软Detours Hook库编译与使用

Detours 是微软开发的一个强大的Windows API钩子库&#xff0c;用于监视和拦截函数调用。它广泛应用于微软产品团队和众多独立软件开发中&#xff0c;旨在无需修改原始代码的情况下实现函数拦截和修改。Detours 在调试、监控、日志记录和性能分析等方面表现出色&#xff0c;已成…

shell命令行解释器—既陌生有熟悉的东西

今天做一个感性的认识来&#xff0c;用一个生活的例子。 你生活在有一条村子里面&#xff0c;在村的东边就是王婆&#xff0c;王婆呢&#xff1f;她主要做什么呢啊&#xff1f;她在村儿里面呢&#xff0c;也不种地啊&#xff0c;那她干什么呢&#xff1f;他主要做帮别人进行婚嫁…

【TabBar嵌套Navigation案例-发现页面-按钮上的图片旋转 Objective-C语言】

一、接下来,我们来做这个,点击以后,让它出一个蓝色的View 1.就是我们示例程序的这种效果, 一点击,让这个按钮旋转,然后呢,再让它出来一个蓝色的View, 首先,我们要去监听它的点击事件,这是第一,我点击以后,我要做一些什么样的操作,要有点击事件, 所以呢,我要把…

JS基础进阶Webs-API、HTML 、DOM

一、JS中的API 1. 定义 JavaScript API是指为JavaScript提供的一组编程接口和对象&#xff0c;用以允许开发者访问和操作Web浏览器或其他JavaScript环境&#xff08;如Node.js&#xff09;提供的特定功能。这些API使得开发者能够编写更加动态和交互式的Web应用程序。 2. 主要…

服务器数据恢复—raid5阵列热备盘未全部启用导致阵列崩溃的数据恢复案例

服务器存储数据恢复环境&#xff1a; 一台EMC某型号存储中有一组RAID5磁盘阵列。该raid5阵列中有12块硬盘&#xff0c;其中2块硬盘为热备盘。 服务器存储故障&#xff1a; 该存储raid5阵列中有两块硬盘离线&#xff0c;只有1块热备盘启用替换掉其中一块离线盘&#xff0c;另外…

​产品经理-​你如何理解“互联网思维(35)

在产品规划和功能改版中&#xff0c;确实非常重视用户需求和体验。产品需求是互联网产品的核心 用户体验是互联网产品的重点。在互联网新产品规划中&#xff0c;会非常重视用户验证环节 确保做出来的东西确实是用户想要的&#xff1b;而在已经上线的产品中&#xff0c;往往会有…

人工智能与机器学习原理精解【12】

文章目录 分级聚类理论分级聚类的详细说明1. 定义2. 算法3. 计算4. 例子5. 例题 皮尔逊相关系数 julia实现 参考文献 分级聚类 理论 分级聚类的详细说明 1. 定义 分级聚类&#xff08;Hierarchical Clustering&#xff09;&#xff0c;又称为层次聚类&#xff0c;是一种通过…

谷歌反垄断官司败诉后,或又面临被拆分风险?

KlipC报道&#xff1a;上周8月5日&#xff0c;美国法院裁定谷歌的搜索业务违反了美国反垄断法&#xff0c;非法垄断在线搜索和搜索文本广告市场。据悉&#xff0c;胜诉的美国司法部正在考虑拆分谷歌。其他选项包括强制谷歌与竞争对手分享更多数据&#xff0c;以及防止其在人工智…

【二叉树进阶】--- 根据二叉树创建字符串

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 数据结构 从本篇文章开始&#xff0c;博主将分享一些结合二叉树的进阶算法题。 &#x1f3e0; 根据二叉树创建字符串 &#x1f4cc; 题目内容 根据二叉…

从行为面试问题(behavioral questions)看中美程序员差异。

中美程序员在职场中的工作状态和职能、福利等有很大区别&#xff0c;从面试中的BQ轮就可见一斑。 中美程序员的面试轮差异&#xff1f; 国内的面试轮在不同公司间差异很大&#xff0c;但总体的问题类型包含笔试面试&#xff08;算法题、概念题、项目深挖、职业目标、职场文化…

FGUI+TS如何实现数字翻滚

FGUITS如何实现数字翻滚 实现效果如下&#xff1a; 实现步骤&#xff1a; fgui制作组件和特效 fgui制作组件&#xff0c;设置一条竖向数字包含1-9或者小数点符号等&#xff0c;可见区域为一个数字大小&#xff0c;最好可见区域紧贴数字&#xff0c;这样滚动的时候滚动区域范围…

深度学习------------------卷积神经网络(LeNet)

目录 LeNet网络手写的数字识别MNIST总结卷积神经网络&#xff08;LeNet&#xff09; 问题 LeNet网络 手写的数字识别 MNIST ①输入的是&#xff1a;3232的image ②放到一个55的卷积层里面&#xff08;为什么是5&#xff1f;因为32-x128&#xff0c;∴x5&#xff09;&#xff0c…

【教程】Ubuntu给pycharm添加侧边栏快捷方式

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 以下教程不仅限于pycharm&#xff0c;其他软件也是一样操作 1、进入到pycharm的目录&#xff0c;先通过命令行打开pycharm&#xff1a; ./bin/pycharm…

keepalived+haproxy高可用负载均衡集群

简介 使用haproxy制作负载均衡集群&#xff0c;keepalived通过状态检测脚本检测本机haproxy状态&#xff0c;若为离线状态&#xff0c;则会降低该节点的优先级。 实验准备 四台虚拟机&#xff1a;KA1、KA2为keepalivedhaproxy&#xff0c;web1、web2为后端服务器&#xff0c;均…

阿里云-java调用短信服务,第三方接口的开启(傻瓜式教程)

第一步&#xff1a;在浏览器中&#xff0c;搜索阿里云 第二步&#xff1a;打开aly的主页 第三步&#xff1a;在最上方的导航栏中&#xff0c;找到云市场&#xff0c;注意不要点击&#xff0c;会自动有触发悬浮框出现&#xff0c;在悬浮框中找到 短信 第四步&#xff1a;点击 短…

无人机之电池注意事项

1、外场作业时&#xff0c;电池一定要放置在阴凉处&#xff0c;避免太阳直射&#xff1b; 2、刚作业完的电池发热严重时&#xff0c;请降至室温再充电&#xff1b; 3、注意电池状态&#xff0c;一旦发现电池出现鼓包、漏液等现象&#xff0c;必须马上停止使用&#xff1b; 4…

UE5 C++项目的配置

创建项目 首先启动UE5,然后选择要创建的项目&#xff0c;选择c进行创建 创建项目完毕之后&#xff0c;会自动打开visual studio&#xff0c;页面如下图所示 点击总体配置状态的刷新按钮&#xff0c;会自动检测总体的配置状态 一般会在下图所示的两项出现警告 Unreal Engi…