如何获取覆盖指定虚函数的对象的动态类型?
问题背景与需求
我需要维护一个集合,用来记录允许被触发的虚函数实现。程序运行时,如果调用的虚函数不在这个集合里,要触发告警。
现有代码如下:
struct Base { virtual void foo() {} }; struct Derive1 : Base { // 未被记录到集合中 void foo() override { Base::foo(); } }; struct Derive2 : Derive1 {}; struct Derive3 : Derive2 { void foo() override {} }; void Caller(Base &o) { // 期望检测到Derive1的foo,但typeid(o)返回Derive2 o.foo(); } int main() { Derive2 x; Caller(x); }
之前尝试过在调用o.foo()前向单例写入唯一键,再在Base::foo()中检查键是否匹配,但因为Derive1::foo()会调用Base::foo(),导致键被误判为已匹配,无法检测到Derive1::foo()未注册的问题。
核心需求是:
- 在调用点(如
Caller函数)能获取实际执行的虚函数所属类型(比如Derive1)的唯一标识 - 在虚函数实现中能获取自身类型的唯一标识
- 两者可匹配,以此校验虚函数是否已注册;若调用点拿到的标识不在集合中,触发告警
解决方案
方案1:通过虚函数表获取实际覆写类型
C++虚函数表中,每个虚函数入口对应实际覆写它的类的实现,我们可以通过虚函数地址反向关联到对应类型。
实现思路
- 为每个覆写
foo的类生成唯一标识(比如用typeid(T).hash_code()),预先注册到集合中。 - 在调用虚函数前,通过对象的虚函数表拿到
foo的实际函数地址,再通过预构建的映射表,把函数地址映射到对应类型标识。 - 检查该标识是否在注册集合中,不在则触发告警。
代码示例
#include <unordered_set> #include <unordered_map> #include <typeinfo> #include <iostream> struct Base { virtual void foo() {} }; struct Derive1 : Base { void foo() override { Base::foo(); } }; struct Derive2 : Derive1 {}; struct Derive3 : Derive2 { void foo() override {} }; // 预注册的合法类型标识集合 std::unordered_set<size_t> registered_types; // 函数地址到类型标识的映射 std::unordered_map<void*, size_t> func_to_type; // 模板函数,注册类型及其虚函数地址 template<typename T> void register_type() { size_t type_hash = typeid(T).hash_code(); registered_types.insert(type_hash); void* func_addr = reinterpret_cast<void*>(&T::foo); func_to_type[func_addr] = type_hash; } // 获取对象o的foo函数实际实现的类型标识 size_t get_actual_foo_type(Base& o) { // 注:虚函数表布局依赖编译器,以下为gcc/clang常见布局 void** vtable = *reinterpret_cast<void***>(&o); void* actual_foo_addr = vtable[0]; // Base::foo是第一个虚函数 return func_to_type.at(actual_foo_addr); } void Caller(Base &o) { size_t actual_type_hash = get_actual_foo_type(o); if (registered_types.find(actual_type_hash) == registered_types.end()) { std::cerr << "告警:未注册的虚函数实现,实际覆写类型:" << typeid(decltype(Derive1{})).name() << std::endl; } o.foo(); } int main() { register_type<Derive3>(); Derive2 x; Caller(x); // 触发告警,Derive1未注册 Derive3 y; Caller(y); // 正常执行 }
方案2:给虚函数添加主动匹配标记
修改每个覆写foo的类,在其函数内主动标记自身已被调用,调用前校验匹配结果。
实现思路
- 定义线程局部变量,存储当前期望检测的类型标识,以及匹配状态。
- 在
Caller中先获取实际覆写foo的类型标识,存入线程局部变量,再调用o.foo()。 - 每个覆写
foo的函数中,检查线程局部变量是否等于自身类型标识,是则标记匹配成功;Base::foo不做匹配处理。 - 调用结束后,若未匹配成功则触发告警。
代码示例
#include <unordered_set> #include <typeinfo> #include <iostream> #include <thread> struct Base { virtual void foo() {} }; struct Derive1 : Base { void foo() override { if (current_expected_type == typeid(Derive1).hash_code()) { matched = true; } Base::foo(); } }; struct Derive2 : Derive1 {}; struct Derive3 : Derive2 { void foo() override { if (current_expected_type == typeid(Derive3).hash_code()) { matched = true; } } }; // 线程局部变量:当前期望的类型标识、匹配状态 thread_local size_t current_expected_type = 0; thread_local bool matched = false; // 预注册集合 std::unordered_set<size_t> registered_types; // 获取实际覆写foo的类型标识(单继承场景下遍历继承链) size_t find_overriding_type(Base& o) { void** obj_vtable = *reinterpret_cast<void***>(&o); void** base_vtable = *reinterpret_cast<void***>(static_cast<Base*>(&o)); // 对比虚函数地址,判断当前类型是否覆写了foo if (obj_vtable[0] != base_vtable[0]) { return typeid(o).hash_code(); } // 递归检查基类(单继承场景简化处理) return find_overriding_type(static_cast<Derive1&>(o)); } void Caller(Base &o) { size_t actual_type_hash = find_overriding_type(o); matched = false; current_expected_type = actual_type_hash; o.foo(); if (!matched && registered_types.count(actual_type_hash) == 0) { std::cerr << "告警:未注册的虚函数实现" << std::endl; } } int main() { registered_types.insert(typeid(Derive3).hash_code()); Derive2 x; Caller(x); // 触发告警,Derive1未注册 Derive3 y; Caller(y); // 正常执行 }
方案3:CRTP自动注册类型
利用奇异递归模板模式(CRTP)让每个覆写foo的类自动注册到集合中,同时生成唯一标识。
实现思路
- 定义CRTP基类继承
Base,在构造函数中自动注册当前类型的标识。 - 每个覆写
foo的类继承该CRTP基类,替代直接继承Base。 - 调用前通过虚函数表获取实际覆写类型的标识,检查是否在注册集合中。
代码示例
#include <unordered_set> #include <unordered_map> #include <typeinfo> #include <iostream> struct Base { virtual void foo() {} }; // CRTP基类,自动注册类型 template<typename Derived> struct RegisterableBase : Base { RegisterableBase() { registered_types.insert(typeid(Derived).hash_code()); // 同时注册虚函数地址与类型的映射 func_map[reinterpret_cast<void*>(&Derived::foo)] = typeid(Derived).hash_code(); } static std::unordered_set<size_t> registered_types; static std::unordered_map<void*, size_t> func_map; }; template<typename Derived> std::unordered_set<size_t> RegisterableBase<Derived>::registered_types; template<typename Derived> std::unordered_map<void*, size_t> RegisterableBase<Derived>::func_map; struct Derive1 : RegisterableBase<Derive1> { void foo() override { Base::foo(); } }; struct Derive2 : Derive1 {}; struct Derive3 : RegisterableBase<Derive3> { void foo() override {} }; // 获取实际覆写foo的类型标识 size_t get_actual_foo_type(Base& o) { void** vtable = *reinterpret_cast<void***>(&o); void* actual_foo_addr = vtable[0]; return RegisterableBase<int>::func_map.at(actual_foo_addr); } void Caller(Base &o) { size_t actual_type_hash = get_actual_foo_type(o); if (!RegisterableBase<int>::registered_types.count(actual_type_hash)) { std::cerr << "告警:未注册的虚函数实现" << std::endl; } o.foo(); } int main() { Derive1 d1; // 自动注册 Derive3 d3; // 自动注册 Derive2 x; Caller(x); // 正常执行,Derive1已注册 }
内容的提问来源于stack exchange,提问作者felix
相关产品推荐
相关产品推荐

