如何在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
相关产品推荐
相关产品推荐

