C++能否仅通过模板参数T查找匹配的特化模板实例?
问题描述
假设我定义了如下代码:
template <typename T, typename ...args> static std::map<T, std::tuple<args...>> MyMaps;
所有类型在编译期都是确定的,每传入一组不同的类型参数,就会生成一个新的map实例。我能否仅通过键以及键的类型(对应模板参数T),在所有生成的map实例中搜索到匹配的实例?
补充说明
我实际的需求是开发一款适用于Unreal的调试辅助工具,用于跟踪对象的数值,可直接引入的头文件debbughelper.h代码如下:
#include <tuple> #include <vector> #include <type_traits> #define WATCHMYOBJECT(object, ...) //TO DO #define RESCANMYOBJECT(object) //TO DO template <typename ...T> void Expand(T...args) { return; } template <typename T, typename ...args> class MyWatcherClass { public: static void WatchMyObject(T &object, args& ...b); static void RescanMyObject(T& MyObject); static std::vector<MyWatcherClass*> Instaces; private: std::tuple<args...> MyTuple = std::tuple<args...>(); std::vector<void*> VoidPointerStorage; T* MyObjectPointer; private: MyWatcherClass(); ~MyWatcherClass(); }; template <typename T, typename ...args> void MyWatcherClass<T, args...>::WatchMyObject(T &MyObject, args& ...b) { MyWatcherClass<T, args...>* MyClassPointer = new MyWatcherClass; InstacedObjects.push_back(MyClassPointer); MyObjectPointer = &MyObject; int helpint = 0; MyClassPointer->MyTuple = std::make_tuple(b...); Expand((MyClassPointer->PointerStorage.push_back((void*)&b),1)...); } template <typename T, typename ...args> void MyWatcherClass<T, args...>::RescanMyObject(T &MyObject) { // 待实现,当前无法调用该方法 // 遍历Instaces[i].MyObjectPointer和&MyObject比较找到匹配实例 // 使用tuple的类型将std::vector<void*> VoidPointerStorage中的空指针转回对应类型指针 // 解引用指针获取数值,更新到屏幕、日志等位置 }
用户的使用方式如下:
// #include "debbughelper.h" class MyNormalClass { public: MyNormalClass(int _MyInt, float _MyFloat, std::string _MyString); int MyInt; float MyFloat; std::string MyString; }; MyNormalClass::MyNormalClass(int _MyInt, float _MyFloat, std::string _MyString) : MyInt(_MyInt), MyFloat(_MyFloat), MyString(_MyString) { } int main() { MyNormalClass MyObject = MyNormalClass(1, 5.2f, std::string("hello")); WATCHMYOBJECT(MyObject, MyObject.MyInt, MyObject.MyFloat, MyObject.MyString); // 执行业务逻辑 RESCANMYOBJECT(MyObject); // 用法简单,不需要重复输入所有成员变量 }
当前存在的问题是:如果不传入成员变量的类型,就无法调用RescanMyObject方法。
解决方案
你遇到的核心问题是:MyWatcherClass<T, Args...>的静态方法RescanMyObject依赖Args参数包,而调用RESCANMYOBJECT时只有对象类型T,无法推导出Args参数包,自然找不到对应的模板实例。
可以通过全局注册+lambda捕获参数类型的方式解决,不需要修改太多原有代码:
步骤1:新增全局注册表
在debbughelper.h中新增全局注册表,存储对象地址和对应的扫描回调:
#include <unordered_map> #include <functional> // 全局注册表:key为被监听对象的地址,value为对应的扫描回调 inline std::unordered_map<void*, std::function<void()>> g_watcherReg;
如果是Unreal环境,也可以替换为TMap<void*, TFunction<void()>>避免STL依赖。
步骤2:实现两个宏定义
利用宏调用时可以拿到所有参数类型的特性,在WATCHMYOBJECT调用时就把RescanMyObject的调用逻辑捕获到lambda中,存入注册表:
#define WATCHMYOBJECT(object, ...) \ do { \ using ObjType = std::remove_reference_t<decltype(object)>; \ MyWatcherClass<ObjType, decltype(__VA_ARGS__)...>::WatchMyObject(object, __VA_ARGS__); \ g_watcherReg[&object] = [&object](){ \ MyWatcherClass<ObjType, decltype(__VA_ARGS__)...>::RescanMyObject(object); \ }; \ } while(0) #define RESCANMYOBJECT(object) \ do { \ auto it = g_watcherReg.find(&object); \ if (it != g_watcherReg.end()) { \ it->second(); \ } \ } while(0)
步骤3:修正原有代码的笔误和实现RescanMyObject
你原有代码存在变量名不一致的问题,先修正,再补全RescanMyObject的逻辑:
// 先声明静态成员变量 template <typename T, typename ...args> std::vector<MyWatcherClass<T, args...>*> MyWatcherClass<T, args...>::Instaces; template <typename T, typename ...args> void MyWatcherClass<T, args...>::WatchMyObject(T &MyObject, args& ...b) { MyWatcherClass<T, args...>* MyClassPointer = new MyWatcherClass; // 修正变量名InstacedObjects为Instaces Instaces.push_back(MyClassPointer); MyClassPointer->MyObjectPointer = &MyObject; MyClassPointer->MyTuple = std::make_tuple(b...); // 修正变量名PointerStorage为VoidPointerStorage Expand((MyClassPointer->VoidPointerStorage.push_back((void*)&b),1)...); } template <typename T, typename ...args> void MyWatcherClass<T, args...>::RescanMyObject(T &MyObject) { // 遍历当前模板实例的所有监听对象 for (auto* instance : Instaces) { if (instance->MyObjectPointer == &MyObject) { // 折叠表达式遍历tuple的类型,把void*转回原类型输出 std::size_t idx = 0; std::apply([&](auto&&... arg) { ( [&]() { using ArgType = std::remove_reference_t<decltype(arg)>; ArgType* ptr = static_cast<ArgType*>(instance->VoidPointerStorage[idx]); // 这里替换为你需要的输出逻辑,比如Unreal的UE_LOG、屏幕打印 UE_LOG(LogTemp, Log, TEXT("值: %s"), *FString(std::to_string(*ptr))); idx++; }(), ... ); }, instance->MyTuple); break; } } }
注意事项
- 调试工具可以忽略小的内存泄漏问题,如果需要完善可以加
UNWATCHMYOBJECT宏,从g_watcherReg和Instaces中移除对应的实例,避免野指针。 - 同一对象不要多次调用
WATCHMYOBJECT,否则后面的注册会覆盖前面的回调。
内容的提问来源于stack exchange,提问作者João Pedro Bezerra Socas
相关产品推荐
相关产品推荐

