提问人:Seb 101 提问时间:2/18/2023 最后编辑:Seb 101 更新时间:2/19/2023 访问量:67
如何在不希望进行静态清理的情况下处理模板类静态成员和存储
How to handle template class static members and storage in situations where leaving to static cleanup isn't desired
问:
在 C# 和 java 领域工作多年后,我最近又回到了 C++,并且喜欢 C++ 在我不在的地方(从 C++11 之前开始,现在我正在吸收 C++20!模板的强大功能对我来说真的很令人兴奋。然而,我很快就遇到了一些似乎无法处理的事情。
我写了一个模板驱动的事件系统,我非常满意。
struct imgui_event_listener
{
void onCreatedWindow(events::ImGui_CreatedWindow f)
{
}
}
dispatcher::connect(&listener, &imgui_event_listener::onCreatedWindow);
...
dispatcher::fire(events::ImGui_CreatedWindow{ window });
我喜欢它,它不需要模板参数,可以对事件使用任意结构。不过,有一个很大的障碍,那就是我的事件系统几乎是无限的、完全不可知的潜在不可见版本,所有这些都使用了一些需要去某个地方的unordered_maps。它们目前在方法中是静态的。
像这样的东西......
template <typename ListenerType, typename Type>
using FPtr = void(ListenerType::*)(Type);
template<typename ListenerType, typename Type>
using Tuple = eastl::tuple<ListenerType*, FPtr<ListenerType, Type>>;
template <typename ListenerType, typename Type>
using ListenerMap = eastl::unordered_map<uint32_t, Tuple<ListenerType, Type>>;
class EventSystem
{
template<typename ListenerType, typename Type>
ListenerMap<ListenerType, Type>& listenerMap()
{
static ListenerMap<ListenerType, Type> listenerMap;
return listenerMap;
}
}
这些天我是一个相对菜鸟,所以如果有什么奇怪的事情,请原谅。
这似乎太方便了,令人满意,不值得推荐,我本以为人们会出于某种原因被大喊大叫,但似乎这是大多数时候必须这样做的方式。
但是我正在使用的游戏引擎有一个内存管理器,并且不使用或推荐 STL,它有自己的替代品,它使用 EA 为游戏开源的那个,并且有自己的分配器,用于它,以便它可以跟踪内存。
这将跟踪所有内存使用情况,并报告在删除静态对象时发生的任何泄漏,但有一个例外。
不幸的是,尽管容器是静态的,但这些unordered_maps的节点是从内存托管堆中分配的。预期的用法是在清理时清除它们,这是其他任何地方都会发生的情况。
现在,我怀疑实际上,这些映射静态实例将在内存管理器已经抱怨后自动处理,但是真的没有真正的方法可以避免它抱怨,即使只是在调试中,退出时出现异常并看到内存地址报告为泄漏很烦人,当它这样做时,这让我感到羞愧!它也可能掩盖真正的问题。我确信我可以通过分配来暴力破解普通的 STL 地图,但我真的很想遵循引擎开发人员铺设的道路,避免将来出现任何麻烦。因此,它更多的是关于便利和虚荣,但我需要一种方法来解决这个问题,同时又不牺牲我的事件系统的优雅使用。
这让我进入了模板元编程的世界,尝试将所有这些模板参数整理到一些大型元组中,因为事件连接的形成和模板实例的创建是在编译时创建的,并研究了每一个可以想象的模式,在这些模式中,我能够以某种方式展开我创建的所有模板实例,以便提前获得这些实例并能够手动清理, 但似乎模板元编程需要为每条指令提供新的“变量名称”作为 typedef 或使用意味着我无法从我所看到的迭代中做到这一点,但老实说,该领域非常复杂且对相关示例的了解非常少,我不知道我是否遗漏了什么。
我试着想想这些还可以存储在哪里,但我就是无法理解它会有什么不同。
我也可以手动维护我所有事件的列表,但这样我就会失去一个更酷、更灵活的功能。
或者我可以将它们存储在其他地方,以便我能够访问它们。这似乎是一个第 22 条军规的情况,他们的存在本身就要求他们处于某个无法进入的平行宇宙中。
所以,是的,我知道有一些方法可以在不寻求帮助的情况下解决这个问题,但它们要么牺牲了系统的功能,要么可能导致未来的问题。不确定我是否曾经在这里问过问题,但这真的难倒了我,我真的无法从其他人那里找到类似的问题,大多数人都乐于使用 std 库,没有内存管理器,并且将静态实例放在函数中是一种超级令人满意的简单方法。
感谢您抽出宝贵时间接受采访!请原谅,如果有点漫无边际,但我觉得我需要提供背景信息。我知道没有太多的代码可以展示,但我希望你能明白我的意思!
答:
也许是这样的:
std::vector<std::function<void()>> cleanup_registry;
class EventSystem
{
template<typename ListenerType, typename Type>
ListenerMap<ListenerType, Type>& listenerMap()
{
static ListenerMap<ListenerType, Type> listenerMap;
static int dummy = (
cleanup_registry.push_back([&]() { listenerMap.clear(); }),
0);
return listenerMap;
}
};
void cleanup() {
for (auto& cleaner : cleanup_registry) {
cleaner();
}
std::vector<std::function<void()>>{}.swap(cleanup_registry);
}
在游戏引擎终止之前调用。cleanup()
您可以使用类似的类型擦除技术从地图中消除,每个事件类型只有一个地图。类似的东西ListenerType
template <typename Event>
using ListenerMap = eastl::unordered_map<uint32_t, std::function<void(Event)>>;
然后(在实际侦听器类型可用的情况下)将生成要放置在映射中的回调 lambda。dispatcher::connect
评论