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
相关产品推荐
相关产品推荐

