如何避免父子类使用虚函数表?寻求运行时初始化设计方案
避免虚函数表的运行时多态方案
方案1:使用std::variant+std::visit(C++17及以上)
这是现代C++中最直接的解决方案,std::variant作为类型安全的联合体存储A或B对象,std::visit会在编译时生成所有可能的调用分支,运行时仅做简单的类型索引判断,完全消除虚函数表的间接调用开销。
示例代码:
#include <variant> #include <iostream> class A { public: void do_something() { std::cout << "A doing something\n"; } }; class B { public: void do_something() { std::cout << "B doing something\n"; } }; using AOrB = std::variant<A, B>; AOrB create_A_or_B(bool flag) { return flag ? A{} : B{}; } int main(int argc, char* argv[]) { bool flag = (argc > 1); // 用命令行参数模拟运行时标志 AOrB ab = create_A_or_B(flag); std::visit([](auto& obj) { obj.do_something(); }, ab); return 0; }
该方案的调用性能与直接调用A::do_something()或B::do_something()几乎一致,无额外运行时损耗。
方案2:函数指针封装(兼容C++03及以上)
若需兼容旧标准,可通过函数指针封装对象行为,替代虚函数表的间接调用逻辑,性能开销比虚函数更小。
示例代码:
#include <iostream> class A { public: void do_something() { std::cout << "A doing something\n"; } }; class B { public: void do_something() { std::cout << "B doing something\n"; } }; // 封装数据与操作的结构体 struct AOrB { void* data; void (*do_something)(void*); }; void do_something_A(void* ptr) { static_cast<A*>(ptr)->do_something(); } void do_something_B(void* ptr) { static_cast<B*>(ptr)->do_something(); } AOrB create_A_or_B(bool flag) { AOrB result; if (flag) { result.data = new A(); result.do_something = do_something_A; } else { result.data = new B(); result.do_something = do_something_B; } return result; } int main(int argc, char* argv[]) { bool flag = (argc > 1); AOrB ab = create_A_or_B(flag); ab.do_something(ab.data); // 直接调用函数指针,无虚函数开销 // 手动释放内存 flag ? delete static_cast<A*>(ab.data) : delete static_cast<B*>(ab.data); return 0; }
此方案需手动管理内存与类型安全,但运行时开销极低。
方案3:基于模板的类型擦除(无虚函数)
通过模板实现类型擦除逻辑,用函数指针模拟虚表行为,所有操作均为编译时生成的模板函数,无虚函数表的间接访问开销,同时保留封装性。
示例代码:
#include <iostream> #include <memory> class A { public: void do_something() { std::cout << "A doing something\n"; } }; class B { public: void do_something() { std::cout << "B doing something\n"; } }; class AOrB { public: template<typename T> AOrB(T obj) : storage(new Model<T>(std::move(obj))) {} void do_something() { storage->invoke_do_something(storage.get()); } private: struct Concept { void (*invoke_do_something)(Concept*); void (*destroy)(Concept*); }; template<typename T> struct Model : Concept { Model(T obj) : data(std::move(obj)) { invoke_do_something = [](Concept* self) { static_cast<Model*>(self)->data.do_something(); }; destroy = [](Concept* self) { delete static_cast<Model*>(self); }; } T data; }; std::unique_ptr<Concept, void(*)(Concept*)> storage{nullptr, [](Concept*){}}; }; AOrB create_A_or_B(bool flag) { return flag ? AOrB(A{}) : AOrB(B{}); } int main(int argc, char* argv[]) { bool flag = (argc > 1); AOrB ab = create_A_or_B(flag); ab.do_something(); return 0; }
该方案兼顾封装性与性能,但代码复杂度略高。
方案对比
std::variant+std::visit:类型安全、内存自动管理、现代C风格,性能接近直接调用,仅需C17及以上支持。- 函数指针封装:兼容旧标准、开销极小,但需手动管理内存,存在类型安全风险。
- 模板类型擦除:封装性好、类型安全、无虚函数开销,但代码复杂度较高,编译时间略长。
内容的提问来源于stack exchange,提问作者Barry Burns
相关产品推荐
相关产品推荐

