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

C++17下基于模板类型擦除存储不同Foo实例的实现方案问询

可行方案说明

你要实现的异构Foo实例存储需求在C++17下有两种常用的落地方式,核心都是通过类型擦除抹平不同模板实例的类型差异:

方案1:基类派生实现类型擦除

适用于无法提前枚举所有Arg参数类型、需要灵活扩展的场景,类型检查在运行时完成。

#include <string>
#include <functional>
#include <unordered_map>
#include <memory>
#include <any>
#include <stdexcept>

// 非模板基类,做类型擦除的统一接口
struct FooBase {
    virtual ~FooBase() = default;
    // 统一调用入口,用std::any传递参数避免类型绑定
    virtual void call(std::any arg_ptr) = 0;
};

template <class Arg>
struct Foo : FooBase
{
    using arg_type = Arg;
    using fun_type = std::function< void(Arg&) >;
    fun_type fun;
    
    void call( Arg& arg ) { fun(arg); }
    // 实现基类的虚接口,做参数类型转换
    void call(std::any arg_ptr) override {
        if (arg_ptr.type() != typeid(Arg*)) {
            throw std::bad_any_cast();
        }
        fun(*std::any_cast<Arg*>(arg_ptr));
    }
};

struct Bar
{
    using map_type = std::unordered_map<std::string, std::unique_ptr<FooBase>>;
    map_type map;
    
    // 插入指定参数类型的Foo实例
    template<typename Arg>
    void insert(std::string name, std::function<void(Arg&)> fun) {
        auto foo = std::make_unique<Foo<Arg>>();
        foo->fun = std::move(fun);
        map[std::move(name)] = std::move(foo);
    }

    // 按名称调用函数,参数类型不匹配会抛出std::bad_any_cast
    template<typename Arg>
    void call(const std::string& name, Arg& arg) {
        auto it = map.find(name);
        if (it == map.end()) {
            throw std::out_of_range("key not found");
        }
        it->second->call(&arg);
    }

    // 主动获取指定类型的Foo实例,可用于直接操作Foo的其他成员
    template<typename Arg>
    Foo<Arg>* get(const std::string& name) {
        auto it = map.find(name);
        if (it == map.end()) return nullptr;
        return dynamic_cast<Foo<Arg>*>(it->second.get());
    }
};

方案优缺点

  • 优点:支持任意参数类型的Foo实例,扩展性极强
  • 缺点:需要堆内存分配,类型检查在运行时完成,有少量性能损耗

方案2:std::variant实现

适用于可以提前枚举所有可能用到的Arg参数类型的场景,类型检查基本在编译期完成,性能更高。

#include <string>
#include <functional>
#include <unordered_map>
#include <variant>
#include <stdexcept>

template <class Arg>
struct Foo
{
    using arg_type = Arg;
    using fun_type = std::function< void(Arg&) >;
    fun_type fun;
    
    void call( Arg& arg ) { fun(arg); }
};

// 提前枚举出所有支持的参数类型对应的Foo实例
using SupportedFooType = std::variant<Foo<int>, Foo<std::string>, Foo<double>>;

struct Bar
{
    using map_type = std::unordered_map<std::string, SupportedFooType>;
    map_type map;
    
    // 插入指定参数类型的Foo实例
    template<typename Arg>
    void insert(std::string name, std::function<void(Arg&)> fun) {
        Foo<Arg> foo;
        foo.fun = std::move(fun);
        map[std::move(name)] = std::move(foo);
    }

    // 按名称调用函数,参数类型不匹配会抛出std::bad_variant_access
    template<typename Arg>
    void call(const std::string& name, Arg& arg) {
        std::get<Foo<Arg>>(map.at(name)).call(arg);
    }
};

方案优缺点

  • 优点:不需要堆内存分配,性能接近原生代码,类型错误大概率能在编译期发现
  • 缺点:必须提前确定所有可能用到的参数类型,扩展性较差

补充说明

你原代码中operator[]直接返回auto&的需求无法直接实现:C++是静态类型语言,函数返回类型必须在编译期确定,而不同Foo<Arg>实例属于完全不同的类型,因此只能通过模板方法或者统一的擦除接口来实现访问逻辑。


内容的提问来源于stack exchange,提问作者Jonathan H

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 21:06:03