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

如何获取覆盖指定虚函数的对象的动态类型?

问题背景与需求

我需要维护一个集合,用来记录允许被触发的虚函数实现。程序运行时,如果调用的虚函数不在这个集合里,要触发告警。

现有代码如下:

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++虚函数表中,每个虚函数入口对应实际覆写它的类的实现,我们可以通过虚函数地址反向关联到对应类型。

实现思路

  1. 为每个覆写foo的类生成唯一标识(比如用typeid(T).hash_code()),预先注册到集合中。
  2. 在调用虚函数前,通过对象的虚函数表拿到foo的实际函数地址,再通过预构建的映射表,把函数地址映射到对应类型标识。
  3. 检查该标识是否在注册集合中,不在则触发告警。

代码示例

#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的类,在其函数内主动标记自身已被调用,调用前校验匹配结果。

实现思路

  1. 定义线程局部变量,存储当前期望检测的类型标识,以及匹配状态。
  2. 在Caller中先获取实际覆写foo的类型标识,存入线程局部变量,再调用o.foo()。
  3. 每个覆写foo的函数中,检查线程局部变量是否等于自身类型标识,是则标记匹配成功;Base::foo不做匹配处理。
  4. 调用结束后,若未匹配成功则触发告警。

代码示例

#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的类自动注册到集合中,同时生成唯一标识。

实现思路

  1. 定义CRTP基类继承Base,在构造函数中自动注册当前类型的标识。
  2. 每个覆写foo的类继承该CRTP基类,替代直接继承Base。
  3. 调用前通过虚函数表获取实际覆写类型的标识,检查是否在注册集合中。

代码示例

#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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 04:05:39