如何将不同特化的Action模板类存入同一C++标准容器?
解决不同模板特化Action存入同一容器的问题
这个问题很典型——不同模板特化的Action是完全独立的类型,C标准容器只能存储同类型的对象,所以我们需要用类型擦除或者多态基类的方式来统一它们的类型。这里我推荐用多态基类的方案,既符合C的常规设计,又能保留每个Action的原有功能。
核心思路
定义一个非模板的抽象基类,让所有Action模板特化版本都继承这个基类。然后容器中存储基类的智能指针(比如std::unique_ptr),这样就能实现不同Action类型的统一存储。同时,我们需要在基类中定义一个通用的执行接口,通过类型擦除(std::any)来适配不同的参数和返回值类型。
完整实现代码
#include <iostream> #include <functional> #include <utility> #include <vector> #include <memory> #include <any> #include <tuple> #include <stdexcept> // 非模板抽象基类,作为所有Action的统一接口 class AbstractAction { public: virtual ~AbstractAction() = default; // 必须声明虚析构,避免内存泄漏 // 通用执行接口:用std::any传递参数,返回std::any类型的结果 virtual std::any execute(std::any args) = 0; }; // 修改原有的Action模板类,继承自AbstractAction template<typename T, typename... Args> class Action : public AbstractAction { private: std::function<T(Args...)> function; public: explicit Action(std::function<T(Args...)> func) : function(std::move(func)) {} // 实现基类的execute接口,解析参数并调用目标函数 std::any execute(std::any args) override { try { // 将std::any转换为参数tuple auto args_tuple = std::any_cast<std::tuple<Args...>>(args); // 用std::apply展开tuple参数,调用function T result = std::apply(function, args_tuple); return std::any(result); } catch (const std::bad_any_cast& e) { throw std::runtime_error("Action执行参数类型不匹配:" + std::string(e.what())); } } // 额外提供类型安全的调用方式,直接使用Action对象时可以用这个 T invoke(Args... args) { return function(std::forward<Args>(args)...); } }; // 测试代码 int main() { // 创建不同特化的Action实例 auto add_action = std::make_unique<Action<int, int, int>>( [](int a, int b) { return a + b; } ); auto print_action = std::make_unique<Action<void, std::string>>( [](const std::string& msg) { std::cout << "输出消息:" << msg << std::endl; } ); auto multiply_action = std::make_unique<Action<double, double>>( [](double x) { return x * 2.5; } ); // 将不同Action存入vector容器(存储基类智能指针) std::vector<std::unique_ptr<AbstractAction>> action_list; action_list.push_back(std::move(add_action)); action_list.push_back(std::move(print_action)); action_list.push_back(std::move(multiply_action)); // 执行第一个Action:计算5+3 auto add_result = action_list[0]->execute(std::make_tuple(5, 3)); std::cout << "加法结果:" << std::any_cast<int>(add_result) << std::endl; // 执行第二个Action:打印字符串 action_list[1]->execute(std::make_tuple("Hello C++!")); // 执行第三个Action:计算4.0*2.5 auto multiply_result = action_list[2]->execute(std::make_tuple(4.0)); std::cout << "乘法结果:" << std::any_cast<double>(multiply_result) << std::endl; return 0; }
关键要点说明
- 虚析构函数:抽象基类必须声明虚析构,否则当容器销毁基类指针时,不会调用子类的析构函数,会导致内存泄漏。
- std::any与std::tuple:用
std::any统一参数和返回值的类型,用std::tuple包装多参数,这样就能适配任意参数列表的Action。 - 智能指针:使用
std::unique_ptr管理内存,避免手动释放指针的麻烦,也更符合现代C++的内存管理规范。 - 类型安全调用:子类的
invoke函数提供了类型安全的调用方式,当你明确知道Action的具体类型时,可以直接调用这个函数,跳过std::any的类型转换开销。
可选扩展
如果你需要按键值查找Action,可以把容器换成std::map<std::string, std::unique_ptr<AbstractAction>>,用字符串作为每个Action的标识:
std::map<std::string, std::unique_ptr<AbstractAction>> action_map; action_map["add"] = std::make_unique<Action<int, int, int>>([](int a, int b){return a+b;}); action_map["print"] = std::make_unique<Action<void, std::string>>([](const std::string& s){std::cout<<s<<std::endl;}); // 调用时根据键查找 action_map["add"]->execute(std::make_tuple(10, 20));
内容的提问来源于stack exchange,提问作者Squerut
相关产品推荐
相关产品推荐

