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

如何避免父子类使用虚函数表?寻求运行时初始化设计方案

避免虚函数表的运行时多态方案

方案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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 06:25:34