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

如何在C++11中基于运行时变量选择CRTP子类实例?

C++11 下基于运行时枚举值分发CRTP子类调用的解决方案

针对你的场景——在C11环境中,根据SomeLogicClass的运行时枚举值mClassId,调用对应CRTP子类实例的doSomething方法,且无法修改现有CRTP结构、不能使用外部库或C17特性——以下是两种可行的解决方案:

方案一:Switch分支+重载函数对象(轻量实现)

核心思路是避免返回不同类型的实例,转而将需要执行的操作封装为重载的函数对象,在switch分支中直接对对应实例执行操作。

修改后的代码实现

#include <iostream>
#include <type_traits>
#include <stdexcept>

enum struct Type : unsigned { base = 0, child1, child2, child3 /* so on*/ };

// CRTP Base(保持原结构不变)
template<typename Child> struct Base {
    void doSomething() { static_cast<Child*>(this)->doSomething_Impl(); }
private:
    Base() = default;
    friend Child;
};

struct Child1 : public Base<Child1> {
    void doSomething_Impl() { std::cout << "Child1 implementation\n"; }
};
struct Child2 : public Base<Child2> {
    void doSomething_Impl() { std::cout << "Child2 implementation\n"; }
};
struct Child3 : public Base<Child3> {
    void doSomething_Impl() { std::cout << "Child3 implementation\n"; }
};

// 封装操作的函数对象(针对每个Child类型重载operator())
struct DoSomethingOp {
    void operator()(Child1& child) const { child.doSomething(); }
    void operator()(Child2& child) const { child.doSomething(); }
    void operator()(Child3& child) const { child.doSomething(); }
};

class SomeLogicClass
{
    Type mClassId{ Type::base };
    Child1 mChild1;
    Child2 mChild2;
    Child3 mChild3;

public:
    Type getId() const { return mClassId; }
    void setId(Type id) { mClassId = id; }

    // 接受任意可调用对象,对当前选中的Child实例执行操作
    template<typename Func>
    void withInstance(Func&& func)
    {
        switch (mClassId)
        {
        case Type::child1: func(mChild1); break;
        case Type::child2: func(mChild2); break;
        case Type::child3: func(mChild3); break;
        default: 
            throw std::invalid_argument("Invalid class ID");
        }
    }
};

void test(SomeLogicClass& ob, Type id)
{
    ob.setId(id);
    ob.withInstance(DoSomethingOp());
}

int main() 
{
    SomeLogicClass ob;    
    test(ob, Type::child1);
    test(ob, Type::child2);
    test(ob, Type::child3);
}

方案特点

  • 实现简单,无额外类层次开销;
  • 新增Child类型时,只需扩展DoSomethingOp的重载和withInstance的switch分支;
  • 完全符合C++11标准,无需依赖任何新特性。

方案二:类型擦除包装器(符合开闭原则)

核心思路是通过类型擦除,将不同CRTP子类实例封装到统一的虚基类接口中,通过枚举值映射到对应的包装器实例,实现运行时分发。

修改后的代码实现

#include <iostream>
#include <type_traits>
#include <stdexcept>
#include <memory>

enum struct Type : unsigned { base = 0, child1, child2, child3 /* so on*/ };

// CRTP Base(保持原结构不变)
template<typename Child> struct Base {
    void doSomething() { static_cast<Child*>(this)->doSomething_Impl(); }
private:
    Base() = default;
    friend Child;
};

struct Child1 : public Base<Child1> {
    void doSomething_Impl() { std::cout << "Child1 implementation\n"; }
};
struct Child2 : public Base<Child2> {
    void doSomething_Impl() { std::cout << "Child2 implementation\n"; }
};
struct Child3 : public Base<Child3> {
    void doSomething_Impl() { std::cout << "Child3 implementation\n"; }
};

// 类型擦除的基类接口
class ChildWrapper {
public:
    virtual ~ChildWrapper() = default;
    virtual void doSomething() = 0;
};

// 针对每个Child类型的包装器实现
template<typename Child>
class ChildWrapperImpl : public ChildWrapper {
    Child* m_child;
public:
    explicit ChildWrapperImpl(Child* child) : m_child(child) {}
    void doSomething() override { m_child->doSomething(); }
};

class SomeLogicClass
{
    Type mClassId{ Type::base };
    Child1 mChild1;
    Child2 mChild2;
    Child3 mChild3;
    // 用数组存储对应枚举值的包装器(枚举值需连续)
    std::unique_ptr<ChildWrapper> m_wrappers[4];

public:
    SomeLogicClass() {
        // 初始化每个枚举值对应的包装器
        m_wrappers[static_cast<unsigned>(Type::child1)] = std::unique_ptr<ChildWrapper>(new ChildWrapperImpl<Child1>(&mChild1));
        m_wrappers[static_cast<unsigned>(Type::child2)] = std::unique_ptr<ChildWrapper>(new ChildWrapperImpl<Child2>(&mChild2));
        m_wrappers[static_cast<unsigned>(Type::child3)] = std::unique_ptr<ChildWrapper>(new ChildWrapperImpl<Child3>(&mChild3));
    }

    Type getId() const { return mClassId; }
    void setId(Type id) { mClassId = id; }

    void doSomething() {
        auto idx = static_cast<unsigned>(mClassId);
        if (idx >= sizeof(m_wrappers)/sizeof(m_wrappers[0]) || !m_wrappers[idx]) {
            throw std::invalid_argument("Invalid class ID");
        }
        m_wrappers[idx]->doSomething();
    }
};

void test(SomeLogicClass& ob, Type id)
{
    ob.setId(id);
    ob.doSomething();
}

int main() 
{
    SomeLogicClass ob;    
    test(ob, Type::child1);
    test(ob, Type::child2);
    test(ob, Type::child3);
}

方案特点

  • 符合开闭原则:新增Child类型时,只需添加对应的ChildWrapperImpl和初始化代码,无需修改现有分发逻辑;
  • 借助虚函数实现类型擦除,有轻微的运行时开销;
  • 代码结构更清晰,适合Child类型较多或频繁新增的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 12:39:23