C++11环境下,MQTT扩展库如何存储任意类型回调函数?
基于C++11实现支持任意回调的MQTT命令库
要在C++11中实现支持任意返回类型、任意参数的回调命令存储与执行,核心思路是利用类型擦除封装不同签名的回调,结合模板和标准库工具实现参数解析与回调调用。以下是具体方案:
1. 核心设计思路
放弃直接在结构体中存储任意类型的函数指针和参数,改用:
- 抽象基类统一所有回调的调用接口(类型擦除)
- 模板派生类封装具体的回调函数和参数解析逻辑
- 手动实现C++11缺失的
index_sequence工具,用于展开tuple参数
2. 实现代码
第一步:补全C++11缺失的index_sequence工具
C++11没有std::index_sequence,需要手动实现:
// C++11兼容的index_sequence实现 template<std::size_t... Indices> struct index_sequence {}; template<std::size_t N, std::size_t... Indices> struct make_index_sequence_impl : make_index_sequence_impl<N-1, N-1, Indices...> {}; template<std::size_t... Indices> struct make_index_sequence_impl<0, Indices...> : index_sequence<Indices...> {}; template<std::size_t N> using make_index_sequence = make_index_sequence_impl<N>;
第二步:定义抽象回调基类
用类型擦除统一所有回调的执行接口:
#include <memory> #include <string> class CommandCallback { public: virtual ~CommandCallback() = default; // 接收MQTT消息字符串,执行回调(若需返回值可修改为返回自定义Any类型) virtual void execute(const std::string& mqtt_msg) = 0; };
第三步:实现模板化的具体回调类
封装具体的回调函数和参数解析逻辑,自动处理参数展开:
#include <functional> #include <tuple> template<typename Func, typename... Args> class TypedCommandCallback : public CommandCallback { public: // 构造函数:传入回调函数和MQTT消息到参数的解析器 TypedCommandCallback(Func func, std::function<std::tuple<Args...>(const std::string&)> parser) : func_(std::move(func)), parser_(std::move(parser)) {} void execute(const std::string& mqtt_msg) override { // 解析MQTT消息为回调所需的参数tuple auto args_tuple = parser_(mqtt_msg); // 展开tuple并调用回调 invoke_with_tuple(func_, args_tuple); } private: Func func_; std::function<std::tuple<Args...>(const std::string&)> parser_; // 辅助函数:展开tuple参数调用函数 template<typename F, typename Tuple, std::size_t... Indices> static void invoke_impl(F&& func, Tuple&& args, index_sequence<Indices...>) { std::forward<F>(func)(std::get<Indices>(std::forward<Tuple>(args))...); } template<typename F, typename Tuple> static void invoke_with_tuple(F&& func, Tuple&& args) { using TupleSize = typename std::decay<Tuple>::type; invoke_impl( std::forward<F>(func), std::forward<Tuple>(args), make_index_sequence<std::tuple_size<TupleSize>::value>() ); } };
第四步:定义Command结构体与命令管理
#include <vector> struct Command { uint64_t alias; std::unique_ptr<CommandCallback> callback; }; // 全局命令数组(实际项目建议封装为类成员) std::vector<Command> g_commands; // 模板注册函数:简化用户注册命令的流程 template<typename Func, typename... Args> void register_mqtt_command(uint64_t alias, Func&& func, std::function<std::tuple<Args...>(const std::string&)> parser) { g_commands.push_back({ alias, std::make_unique<TypedCommandCallback<Func, Args...>>( std::forward<Func>(func), std::move(parser) ) }); }
3. 用户使用示例
示例1:普通函数回调
#include <iostream> // 用户自定义回调 void print_integer(int num) { std::cout << "Received integer: " << num << std::endl; } // 自定义MQTT消息解析器:将字符串转为int std::tuple<int> parse_int_msg(const std::string& msg) { return std::make_tuple(std::stoi(msg)); } // 注册命令 int main() { register_mqtt_command(1001, print_integer, parse_int_msg); // 模拟收到MQTT消息并执行 for (auto& cmd : g_commands) { if (cmd.alias == 1001) { cmd.callback->execute("12345"); } } return 0; }
示例2:成员函数回调
class Device { public: void set_power(bool on) { std::cout << "Device power " << (on ? "ON" : "OFF") << std::endl; } }; // 解析开关命令 std::tuple<bool> parse_power_msg(const std::string& msg) { return std::make_tuple(msg == "ON"); } int main() { Device my_device; // 用std::bind绑定成员函数 register_mqtt_command(1002, std::bind(&Device::set_power, &my_device, std::placeholders::_1), parse_power_msg ); // 模拟执行 for (auto& cmd : g_commands) { if (cmd.alias == 1002) { cmd.callback->execute("ON"); } } return 0; }
示例3:无参数回调
void hello_world() { std::cout << "Hello World!" << std::endl; } // 无参数解析器返回空tuple std::tuple<> parse_empty_msg(const std::string&) { return std::tuple<>(); } int main() { register_mqtt_command(1003, hello_world, parse_empty_msg); // 模拟执行 for (auto& cmd : g_commands) { if (cmd.alias == 1003) { cmd.callback->execute(""); } } return 0; }
4. 扩展说明
- 返回值处理:若需获取回调返回值,可将
CommandCallback::execute改为返回自定义的Any类型(C++11无std::any,需手动实现简单的类型擦除容器)。 - 线程安全:若库涉及多线程调用,需对命令数组的读写操作加锁(如用
std::mutex)。 - 参数格式:参数解析逻辑由用户自定义,支持JSON、CSV等任意消息格式,灵活性更高。
内容的提问来源于stack exchange,提问作者J.Meulenbeld
相关产品推荐
相关产品推荐

