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

运行时基于枚举值选择类实例化的C++实现问题

问题描述

想要根据enum class的运行时值,构造对应子类的对象并返回基类的shared_ptr。类A和B拥有多个参数数量不同的构造函数,这些构造函数可能在运行时被调用。但当前的工厂方法实现会触发编译错误:

给出的代码实现:

enum class Type
{
    A,
    B // 可扩展更多类型
};

struct Base{};

struct A : public Base
{
    A(const int a) { std::cout << "A::A(int)"; }
    A(double d) { std::cout << "A::A(double)"; }
    A(int a, double d) { std::cout << "A::A(int,double)"; }
};

struct B : public Base
{
    B(int i, double d) { std::cout << "B::B(double)"; }
    B(int a, double d, double dd) { std::cout << "B::B(int,double, double)"; }
};

template <typename... Args>
std::shared_ptr<Base> make_Type(Type t, Args &&...args)
{
    if (t == Type::A)
        return std::make_shared<A>(std::forward<Args>(args)...);
    else if (t == Type::B)
        return std::make_shared<B>(std::forward<Args>(args)...);
    else
        assert(false);
}

调用make_Type(Type::A,1);时,GCC14会报错:

error: no matching function for call to 'B::B(int)'

原因是编译器会检查所有分支的代码合法性,即使运行时不会走到B的分支。已知可以用if constexpr实现静态类型选择,但需要运行时版本的实现(尽量不使用typeinfo),求可行的替代方案。


解决方案

方案1:为不同参数签名重载工厂函数

针对每种可能的参数组合,单独编写工厂函数的重载版本。每个重载只处理当前参数组合下合法的类型分支:

#include <memory>
#include <iostream>
#include <cassert>

enum class Type
{
    A,
    B
};

struct Base{};

struct A : public Base
{
    A(const int a) { std::cout << "A::A(int)\n"; }
    A(double d) { std::cout << "A::A(double)\n"; }
    A(int a, double d) { std::cout << "A::A(int,double)\n"; }
};

struct B : public Base
{
    B(int i, double d) { std::cout << "B::B(int,double)\n"; }
    B(int a, double d, double dd) { std::cout << "B::B(int,double,double)\n"; }
};

// 单int参数的工厂重载
std::shared_ptr<Base> make_Type(Type t, int a)
{
    if (t == Type::A)
        return std::make_shared<A>(a);
    else
        assert(false); // B不支持单int参数,直接触发断言
}

// 单double参数的工厂重载
std::shared_ptr<Base> make_Type(Type t, double d)
{
    if (t == Type::A)
        return std::make_shared<A>(d);
    else
        assert(false);
}

// int+double参数的工厂重载
std::shared_ptr<Base> make_Type(Type t, int a, double d)
{
    if (t == Type::A)
        return std::make_shared<A>(a, d);
    else if (t == Type::B)
        return std::make_shared<B>(a, d);
    else
        assert(false);
}

// int+double+double参数的工厂重载
std::shared_ptr<Base> make_Type(Type t, int a, double d, double dd)
{
    if (t == Type::B)
        return std::make_shared<B>(a, d, dd);
    else
        assert(false);
}

优缺点:实现简单直观,编译期就能检查参数与构造函数的匹配性;但如果参数组合较多,需要编写大量重载函数,扩展性较差。

方案2:使用函数表注册构造逻辑

提前为每种类型和参数组合注册对应的构造函数,运行时通过类型标识和参数签名查找并调用构造逻辑:

#include <memory>
#include <iostream>
#include <cassert>
#include <unordered_map>
#include <tuple>
#include <typeindex>
#include <functional>

enum class Type
{
    A,
    B
};

struct Base{};

struct A : public Base
{
    A(const int a) { std::cout << "A::A(int)\n"; }
    A(double d) { std::cout << "A::A(double)\n"; }
    A(int a, double d) { std::cout << "A::A(int,double)\n"; }
};

struct B : public Base
{
    B(int i, double d) { std::cout << "B::B(int,double)\n"; }
    B(int a, double d, double dd) { std::cout << "B::B(int,double,double)\n"; }
};

// 定义构造函数的函数类型
template <typename... Args>
using Constructor = std::function<std::shared_ptr<Base>(Args&&...)>;

// 全局函数表:键为(Type, 参数类型元组的type_index),值为构造函数指针
std::unordered_map<
    std::pair<Type, std::type_index>, 
    void*,
    decltype([](const auto& k) {
        return std::hash<int>()(static_cast<int>(k.first)) ^ 
               std::hash<std::type_index>()(k.second);
    })
> g_constructor_table;

// 注册构造函数的模板工具
template <typename Derived, typename... Args>
void register_constructor(Type t)
{
    using ArgsTuple = std::tuple<std::decay_t<Args>...>;
    auto key = std::make_pair(t, std::type_index(typeid(ArgsTuple)));
    g_constructor_table[key] = reinterpret_cast<void*>(
        new Constructor<Args...>([](Args&&... args) {
            return std::make_shared<Derived>(std::forward<Args>(args)...);
        })
    );
}

// 工厂函数实现
template <typename... Args>
std::shared_ptr<Base> make_Type(Type t, Args&&... args)
{
    using ArgsTuple = std::tuple<std::decay_t<Args>...>;
    auto key = std::make_pair(t, std::type_index(typeid(ArgsTuple)));
    
    auto it = g_constructor_table.find(key);
    if (it == g_constructor_table.end())
    {
        assert(false);
        return nullptr;
    }

    auto ctor = *reinterpret_cast<Constructor<Args...>*>(it->second);
    return ctor(std::forward<Args>(args)...);
}

// 初始化所有构造函数注册项
void init_constructors()
{
    register_constructor<A, int>(Type::A);
    register_constructor<A, double>(Type::A);
    register_constructor<A, int, double>(Type::A);
    register_constructor<B, int, double>(Type::B);
    register_constructor<B, int, double, double>(Type::B);
}

// 使用示例
int main()
{
    init_constructors();
    auto a1 = make_Type(Type::A, 1);
    auto b1 = make_Type(Type::B, 2, 3.14);
    return 0;
}

优缺点:扩展性强,新增类型或构造函数只需添加注册代码即可;但需要提前初始化函数表,且用到了std::type_index(仅用于区分参数签名,未用于类型判断)。

方案3:用std::variant打包参数(C++17+)

将参数打包为std::variant,利用std::visit和if constexpr在编译期过滤不合法的构造分支,同时保留运行时类型选择:

#include <memory>
#include <iostream>
#include <cassert>
#include <variant>
#include <tuple>

enum class Type
{
    A,
    B
};

struct Base{};

struct A : public Base
{
    A(const int a) { std::cout << "A::A(int)\n"; }
    A(double d) { std::cout << "A::A(double)\n"; }
    A(int a, double d) { std::cout << "A::A(int,double)\n"; }
};

struct B : public Base
{
    B(int i, double d) { std::cout << "B::B(int,double)\n"; }
    B(int a, double d, double dd) { std::cout << "B::B(int,double,double)\n"; }
};

// 定义所有支持的参数组合变体
using ArgsVariant = std::variant<
    std::tuple<int>,
    std::tuple<double>,
    std::tuple<int, double>,
    std::tuple<int, double, double>
>;

std::shared_ptr<Base> make_Type(Type t, ArgsVariant args)
{
    return std::visit([t](auto&& tuple_args) {
        return std::apply([t](auto&&... args) {
            if (t == Type::A)
            {
                if constexpr (std::is_constructible_v<A, decltype(args)...>)
                {
                    return std::make_shared<A>(std::forward<decltype(args)>(args)...);
                }
                else
                {
                    assert(false);
                    return nullptr;
                }
            }
            else if (t == Type::B)
            {
                if constexpr (std::is_constructible_v<B, decltype(args)...>)
                {
                    return std::make_shared<B>(std::forward<decltype(args)>(args)...);
                }
                else
                {
                    assert(false);
                    return nullptr;
                }
            }
            else
            {
                assert(false);
                return nullptr;
            }
        }, std::forward<decltype(tuple_args)>(tuple_args));
    }, std::forward<ArgsVariant>(args));
}

// 使用示例
int main()
{
    auto a1 = make_Type(Type::A, ArgsVariant{std::make_tuple(1)});
    auto b1 = make_Type(Type::B, ArgsVariant{std::make_tuple(2, 3.14)});
    return 0;
}

优缺点:结合了编译期检查和运行时选择的优点,无需编写大量重载;但需要预先定义所有支持的参数组合变体,新增参数组合时需更新ArgsVariant类型。


内容的提问来源于stack exchange,提问作者Arnab Mahanti

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 17:44:56