在无序映射中插入智能指针调用析构函数

Posted

技术标签:

【中文标题】在无序映射中插入智能指针调用析构函数【英文标题】:Inserting a Smart Pointer in a Unordered Map calls destructor 【发布时间】:2021-09-26 08:32:58 【问题描述】:

我正在制作一个引擎并处理材质的东西,我有一个静态渲染器类,用于存储 static std::unordered_map<uint, Ref<Material>> m_Materials;(材质及其 ID),作为 Ref 使用下一个方法的共享指针:

template<typename T>
using Ref = std::shared_ptr<T>;

template<typename T, typename ... Args>
constexpr Ref<T> CreateRef(Args&& ... args)  return std::make_shared<T>(std::forward<Args>(args)...); 

template<typename T>
constexpr Ref<T> CreateRef(T* t)  return std::shared_ptr<T>(t); 

所以一个 Material 是一个简单的类,用一个 unsigned int (uint) 来存储它的 ID 和一个字符串来存储它的名称,并创建它们并存储它们,我在渲染器中有下一个静态函数:

Ref<Material> Renderer::CreateMaterialWithID(uint material_id, const std::string& name)

    Ref<Material> mat = CreateRef<Material>(new Material(material_id, name));
    std::pair<uint, Ref<Material>> mat_pair(material_id, mat);

    m_Materials.insert(mat_pair);
    return mat;

问题是插入到map中,由于某种原因,调用了材质析构函数,导致插入了一个空的智能指针(虽然ID被正确插入了)[material_id, empty]

现在,材质和创建的对似乎正确存储了材质的数据,所以我不知道为什么地图失败了。我还尝试插入作为初始化列表(material_id, mat),将材料创建为CreateRef&lt;Material&gt;(material_id, name)(没有new),还使用emplace而不是insert,但我总是得到相同的结果,而且我不明白为什么地图会调用析构函数。

有人发现有问题吗?

【问题讨论】:

Offtopic:在构造函数上使用CreateRef,如下所示:Foo::Foo(Logger* loger) The problem is that the insertion into the map, for some reason, calls the material destructor 也许已经有一些材料与material_id 相同。在这种情况下,材料不会更新,Material 会被清理。 我试图重现但失败了:Demo on coliru。但是,我必须承认我使用 -std=c++17 进行了测试,其中复制省略是强制性的。我还注意到 MSVC 和调试模式下的一些奇怪影响:copy elision in MSVC & Debug。 在过去(在我开始使用 C++17 之前),关于地图,我有时会使用 std::piecewise_construct 来解决问题。仅供参考:SO: C++11 use-case for piecewise_construct of pair and tuple? 嘿,我终于修好了。 @MarekR 的评论让我觉得他说的是不可能的,因为我检查了该材料是否存在,但是当我仔细观察它时,我发现插入的第一个材料(默认的,通过另一个函数)是正确插入,而后续(通过我发布的功能)没有......所以我发现提到的检查是错误的,它正在添加一个空值的键,并且在进入发布的功能时,材料ID是已经在带有空值的地图中(通过 [] 可以工作) 【参考方案1】:

尝试拨打insert_or_assign;如果密钥已经存在,insert 将简单地破坏您尝试插入的对象。举个例子

m_Materials[0];
m_Materials.insert(0, std::make_shared<Material>(name));

shared_ptr 将被丢弃,因为第一个 [0] 创建了一个 nullptr 值,稍后插入看到它并说“如果它已经存在就不要插入”。

m_Materials.insert_or_assign(0, std::make_shared<Material>(name));

替换存储在那里的 nullptr,就像那样

m_Materials[0]=std::make_shared<Material>(name);

顺便说一句,我非常担心CreateRef 有两个重载,一个接受构造函数参数,另一个接受指针。这是一个混淆地图和领土的例子,我发现它在烦人的时候会让你在一个不舒服的地方咬你。

【讨论】:

以上是关于在无序映射中插入智能指针调用析构函数的主要内容,如果未能解决你的问题,请参考以下文章

垃圾回收器设计

当类没有析构函数时,智能指针或作用域指针会删除对象吗

C++面经漏洞汇总

智能指针简介

硬核 | C++ 基础大全

智能指针之atuo_ptr源码剖析