You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将不同特化的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;
}

关键要点说明

  1. 虚析构函数:抽象基类必须声明虚析构,否则当容器销毁基类指针时,不会调用子类的析构函数,会导致内存泄漏。
  2. std::any与std::tuple:用std::any统一参数和返回值的类型,用std::tuple包装多参数,这样就能适配任意参数列表的Action。
  3. 智能指针:使用std::unique_ptr管理内存,避免手动释放指针的麻烦,也更符合现代C++的内存管理规范。
  4. 类型安全调用:子类的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:08:24