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

C++无公共父类的不同类同名方法通过void*调用方案

核心结论
  • 你想实现的「不判定void*指向类型、不转换为对应类指针就直接调用SetPos」的操作完全不可行。C++成员函数调用必须依赖编译期可知的静态类型信息,void*仅存储内存地址,不携带任何对象内存布局、成员函数入口、调用约定相关的信息,直接调用连编译阶段都无法通过;就算用强制类型转换绕开编译检查,也属于纯粹的未定义行为,运行时必然出现不可预期的错误。
  • 借助模板+类型擦除特性完全可以实现需求,且不需要修改原有Camera、Light、Model三个类的定义,不需要让三类继承公共纯虚接口,对原有业务代码侵入性为零。
无公共继承的可行实现方案

方案1:轻量类型擦除包装器(零额外开销,性能优先场景推荐)

放弃在map中存储裸void*,改为存储自定义的类型擦除包装对象:包装对象构造时通过模板自动绑定对应类型的SetPos调用逻辑,对外暴露统一的SetPos接口,运行时不需要做任何类型判断、不需要RTTI支持,性能和直接调用原生成员函数几乎一致,比虚函数继承方案少一次虚表寻址开销。
示例代码:

#include <map>
#include <string>

// 原有三个业务类,不需要做任何修改
class Camera {
public:
    void SetPos(float x, float y, float z) { /* 原有位置设置逻辑 */ }
};
class Light {
public:
    void SetPos(float x, float y, float z) { /* 原有位置设置逻辑 */ }
};
class Model {
public:
    void SetPos(float x, float y, float z) { /* 原有位置设置逻辑 */ }
};

// 通用类型擦除包装类
class SceneObject {
private:
    using SetPosCaller = void(*)(void* instance, float x, float y, float z);
    void* m_instance;
    SetPosCaller m_caller;

    // 编译期为每个类型生成专属的跳板调用函数
    template<typename T>
    static void CallSetPos(void* instance, float x, float y, float z) {
        static_cast<T*>(instance)->SetPos(x, y, z);
    }
public:
    template<typename T>
    SceneObject(T* obj) : m_instance(obj), m_caller(&CallSetPos<T>) {}

    // 对外统一调用接口
    void SetPos(float x, float y, float z) {
        m_caller(m_instance, x, y, z);
    }
};

// 使用示例
int main() {
    std::map<std::string, SceneObject> object_map;
    Camera main_cam;
    Light dir_light;
    Model player_model;

    object_map.emplace("main_cam", &main_cam);
    object_map.emplace("dir_light", &dir_light);
    object_map.emplace("player_model", &player_model);

    // 统一调用,不需要判断类型、不需要手动强转
    object_map["main_cam"].SetPos(0.f, 1.2f, 6.f);
    object_map["dir_light"].SetPos(20.f, 30.f, 10.f);
    object_map["player_model"].SetPos(0.f, 0.f, 0.f);
    return 0;
}

方案2:std::function封装(代码最简洁,普通业务场景推荐)

如果对性能没有极致要求,可以直接在map中存储签名匹配的std::function对象,不需要自定义包装类,代码量极少:

#include <map>
#include <string>
#include <functional>

std::map<std::string, std::function<void(float, float, float)>> object_map;

int main() {
    Camera main_cam;
    Light dir_light;
    Model player_model;

    // 存入时绑定对应实例的SetPos方法
    object_map["main_cam"] = [&main_cam](float x, float y, float z) {
        main_cam.SetPos(x, y, z);
    };
    object_map["dir_light"] = std::bind(
        &Light::SetPos, &dir_light,
        std::placeholders::_1, std::placeholders::_2, std::placeholders::_3
    );

    // 直接传参调用即可
    object_map["main_cam"](0.f, 1.2f, 6.f);
    return 0;
}

该方案仅存在可忽略的std::function内部开销,不需要手写类型擦除逻辑,适合绝大多数业务场景。

不推荐使用「给void*配套类型枚举、调用时分支判断强转」的实现方式:该方案没有任何编译期类型检查,一旦枚举标记和实际指针类型不匹配就会触发难以排查的内存错误,维护成本随类型增加线性上涨。

内容的提问来源于stack exchange,提问作者krz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 04:39:18