如何在运行时选择CRTP子类并规避多态虚调用开销?
解决方案:CRTP结合运行时类型选择的替代方案
CRTP是静态多态(编译期绑定)机制,而运行时选择子类属于动态行为,二者本身存在天然矛盾。要在避免虚调用开销的前提下实现需求,有以下几种可行方案,void*虽能工作但不推荐:
1. 极度不推荐:void*指针方案
用void*存储子类指针确实能编译通过,但会完全丧失类型安全,且需要手动维护类型标记(如枚举)来强制转换回原类型,一旦类型匹配错误就会触发未定义行为,调试和维护成本极高。除非是极端性能场景且能100%保证类型正确性,否则绝不建议使用。
示例代码(仅作演示,不推荐):
#include <iostream> enum class ChildType { Child1, Child2 }; template<class CHILD> struct Parent { void do_something() { static_cast<CHILD*>(this)->impl_do_something(); } }; struct Child1 : public Parent<Child1> { void impl_do_something() { std::cout << "Child1 doing something\n"; } }; struct Child2 : public Parent<Child2> { void impl_do_something() { std::cout << "Child2 doing something\n"; } }; int main() { bool something_at_runtime = true; void* ptr = nullptr; ChildType type; if (something_at_runtime) { ptr = new Child1(); type = ChildType::Child1; } else { ptr = new Child2(); type = ChildType::Child2; } // 手动匹配类型并调用 switch (type) { case ChildType::Child1: static_cast<Child1*>(ptr)->do_something(); delete static_cast<Child1*>(ptr); break; case ChildType::Child2: static_cast<Child2*>(ptr)->do_something(); delete static_cast<Child2*>(ptr); break; } return 0; }
2. 推荐方案:std::variant(C++17+)
std::variant是类型安全的联合类型,可在运行时存储不同的子类实例,配合std::visit实现编译期绑定的函数调用,完全避免虚函数开销,同时保证类型安全。std::visit的运行时开销仅为基于类型索引的跳转,性能接近直接调用。
示例代码:
#include <variant> #include <iostream> template<class CHILD> struct Parent { void do_something() { static_cast<CHILD*>(this)->impl_do_something(); } }; struct Child1 : public Parent<Child1> { void impl_do_something() { std::cout << "Child1 doing something\n"; } }; struct Child2 : public Parent<Child2> { void impl_do_something() { std::cout << "Child2 doing something\n"; } }; int main() { bool something_at_runtime = true; // 运行时条件 std::variant<Child1, Child2> child_var; // 运行时选择子类 if (something_at_runtime) { child_var = Child1(); } else { child_var = Child2(); } // 调用对应子类的方法,无虚调用开销 std::visit([](auto& obj) { obj.do_something(); }, child_var); return 0; }
3. 折中方案:手动实现函数指针表
如果需要保留传统基类指针的用法,可以定义一个非模板的基类,通过CRTP在编译期初始化基类中的函数指针,替代编译器自动生成的虚表。这种方案既支持运行时多态,又能避免虚函数的额外开销(如虚表访问的缓存开销)。
示例代码:
#include <iostream> // 非模板基类,存储函数指针 struct Base { void (*do_something)(Base*); }; template<class CHILD> struct Parent : public Base { Parent() { // 编译期绑定函数指针到子类实现 do_something = [](Base* self) { static_cast<CHILD*>(self)->impl_do_something(); }; } }; struct Child1 : public Parent<Child1> { void impl_do_something() { std::cout << "Child1 doing something\n"; } }; struct Child2 : public Parent<Child2> { void impl_do_something() { std::cout << "Child2 doing something\n"; } }; int main() { bool something_at_runtime = false; Base* base_ptr = nullptr; if (something_at_runtime) { base_ptr = new Child1(); } else { base_ptr = new Child2(); } // 通过函数指针调用,无虚函数开销 base_ptr->do_something(base_ptr); delete base_ptr; return 0; }
内容的提问来源于stack exchange,提问作者intrigued_66
相关产品推荐
相关产品推荐

