C++基于CRTP的无虚函数高效模块化系统设计问题求解
解决方案
核心思路
由于要求无虚函数、极致性能,我们采用编译期绑定+零开销调用的设计,完全规避运行期类型擦除的额外成本,同时解决参数冗余问题。
完整可运行实现
基础定义
#include <iostream> #include <tuple> #include <functional> // 示例FunctionType枚举与Function类定义 enum FunctionType { FUNC1, FUNC2 }; template<FunctionType funcType> struct Function { int doFunction(int param) const { return param * 2; // 示例实现 } }; // CRTP基类保持不变 template<class Derived> class GenericModule { private: Derived* crtp = static_cast<Derived*>(this); public: void doSomething() { crtp->doSomething(); } void undoSomething() { crtp->undoSomething(); } }; // 模块定义保持不变 template<FunctionType funcType, int param> class ModuleA : public GenericModule<ModuleA<funcType, param>> { private: Function<funcType> func; public: void doSomething() { std::cout << param + 1 << "\n"; } void undoSomething() { std::cout << func.doFunction(param) << "\n"; } }; template<FunctionType funcType, int param1, int param2> class ModuleB : public GenericModule<ModuleB<funcType, param1, param2>> { private: Function<funcType> func; public: void doSomething() { std::cout << param1 + param2 << "\n"; } void undoSomething() { std::cout << func.doFunction(param1 + param2) << "\n"; } };
方案1:编译期固定模块列表(性能最优)
完全编译期展开,开销等价于手动挨个调用模块方法,无任何额外成本:
template<FunctionType FuncType, typename... Modules> class System { private: std::tuple<Modules...> modules; template<typename F> void invoke_all(F&& call) { (std::invoke(call, std::get<Modules>(modules)), ...); } public: explicit System(Modules... mods) : modules(std::move(mods)...) {} // 内嵌别名自动注入FuncType,解决参数冗余问题 template<int param> using ModuleA = ::ModuleA<FuncType, param>; template<int p1, int p2> using ModuleB = ::ModuleB<FuncType, p1, p2>; // 快速创建模块的工厂方法 template<int param> static auto make_moduleA() { return ModuleA<param>{}; } template<int p1, int p2> static auto make_moduleB() { return ModuleB<p1, p2>{}; } void doEverything() { invoke_all([](auto& mod) { mod.doSomething(); }); } void undoEverything() { invoke_all([](auto& mod) { mod.undoSomething(); }); } }; // 使用示例 int main() { // 无需重复指定FUNC1,所有模块自动绑定系统的FunctionType auto system = System<FUNC1>( System<FUNC1>::make_moduleA<1>(), System<FUNC1>::make_moduleB<1, 2>() ); system.doEverything(); // 输出 2、3 system.undoEverything(); // 输出 2、6 return 0; }
方案2:运行期动态添加模块(灵活轻量)
如果需要运行期动态增减模块,可采用函数指针数组存储调用入口,无虚函数,开销仅为vector遍历成本:
template<FunctionType FuncType> class DynamicSystem { private: std::vector<void(*)()> do_ops; std::vector<void(*)()> undo_ops; public: // 同样提供内嵌别名避免参数重复 template<int param> using ModuleA = ::ModuleA<FuncType, param>; template<int p1, int p2> using ModuleB = ::ModuleB<FuncType, p1, p2>; template<typename Derived> void add(GenericModule<Derived>& mod) { do_ops.push_back([&mod]() { mod.doSomething(); }); undo_ops.push_back([&mod]() { mod.undoSomething(); }); } void doEverything() { for (auto& op : do_ops) op(); } void undoEverything() { // 如需逆序undo可反向遍历 for (auto& op : undo_ops) op(); } }; // 使用示例 int main() { DynamicSystem<FUNC1> system; DynamicSystem<FUNC1>::ModuleA<1> m1; DynamicSystem<FUNC1>::ModuleB<1,2> m2; system.add(m1); system.add(m2); system.doEverything(); system.undoEverything(); return 0; }
问题解决说明
- 模块注册/存储问题:
- 编译期方案用
std::tuple做类型安全的异质容器,折叠表达式展开调用,编译器会直接优化为顺序调用模块方法,无任何运行期开销 - 动态方案用无捕获lambda转函数指针存储调用入口,比虚函数表调用开销更低,每个模块仅占用两个指针的存储成本
- 编译期方案用
- FunctionType参数冗余问题:
- 两个方案都通过System类的内嵌模板别名自动注入
FuncType参数,实例化模块时仅需传入模块自身的参数,编译器会强制所有模块的FunctionType与所属系统一致,避免参数不匹配错误
- 两个方案都通过System类的内嵌模板别名自动注入
内容的提问来源于stack exchange,提问作者skudo
相关产品推荐
相关产品推荐

