【问题标题】:Fixing collision memory leak in std::map修复 std::map 中的碰撞内存泄漏
【发布时间】:2015-10-26 13:19:39
【问题描述】:

我遇到了这种问题:可能内存泄漏。
想象一下,如果您为同一个键存储两个不同的堆指针。

#include <map>

std::map<int, int*> myMap;

void Store(int key, int* value)
{
    myMap[key] = value;
}

int main()
{
   Store(42, new int(42));
   Store(35, new int(35));
   delete myMap[42];
   delete myMap[35];
}

我想过这样解决:

#include <map>

std::map<int, int*> myMap;

void Store(int key, int* value)
{
    auto it = myMap.find(key);
    if (it != myMap.end()))
    {
        delete it->second;
        it->second = value;
    }
    else
    {
        myMap[key] = value;
    }
}

int main()
{
   Store(42, new int(42));
   Store(35, new int(35));
   delete myMap[42];
   delete myMap[35];
}

但是现在有两个对数查找而不是一个...
然后我想到了下面这段代码,

#include <map>

std::map<int, int*> myMap;

void Store(int key, int* value)
{
    auto& mappedValue = myMap[key];
    if (mappedValue == nullptr)
    {
        mappedValue = value;
    }
    else
    {
        delete mappedValue;
        mappedValue = value;
    }
}

int main()
{
   Store(42, new int(42));
   Store(35, new int(35));
   delete myMap[42];
   delete myMap[35];
}

但是如果没有关联的值,我怎么能确定 mappedValue 总是指向 nullptr 呢? 您建议如何解决内存泄漏并坚持对数复杂性?

编辑:重构成本非常高,我正在寻找没有智能指针的解决方案。

【问题讨论】:

  • 为什么要存储int* 而不仅仅是int
  • 这是遗留代码。重构的成本很高。
  • “可能存在内存泄漏。”你可以加强这种说法。第一段代码为确定内存泄漏。
  • 好的抱歉我发布的误导性代码,我现在正在编辑它。
  • 不使用智能指针将您带到这里,这是有时间成本的。您所做的每项更改都需要调试和测试以证明正确的释放(以及不使用释放的对象)。重构以使用智能指针最终将被证明是一个数量级的更少成本。

标签: c++ performance dictionary memory-leaks


【解决方案1】:

但是如果没有关联的值,我怎么能确定 mappedValue 总是指向 nullptr 呢?

如果operator[] 添加了一个新元素,则为value-initialized。对于作为指针的非类类型,这相当于零初始化,如果是指针,则零初始化又是nullptr。见std::map::operator[]

【讨论】:

  • 太棒了,这是标准中的表述吗?
  • 值初始化见 §8.5 8.4,零初始化是 §8.5 6.1(§4.10 1 状态 空指针常量是一个值为零的整数文字 (2.13.2) i>) (来自 N4296)
  • @LeFlou: §23.4.4.3: T&amp; operator[](const key_type&amp; x); (...) 如果映射中没有与 x 等效的键,则将 value_type(x, T()) 插入映射中。
【解决方案2】:

您可以考虑使用RAII in the form of smart pointers,而是将您的地图定义为

#include <map>
#include <memory>

std::map<int, std::shared_ptr<int>> myMap;

(为此,请确保将编译器配置为c++11 模式。)

你的映射变成这样:将整数映射到一个类似指针的对象,该对象将负责解除分配。

【讨论】:

  • 感谢您的建议,但我想知道如何在没有智能指针的情况下解决此问题。
  • 在这种情况下,也许您应该解释一下是什么设置导致您不愿意使用共享指针。
  • 我发布了一小部分代码,针对 SO 进行了修改。重构成本很高,我正在寻找另一种方式。
【解决方案3】:

使用新标准 c++11,std::unique_ptr 是您的朋友。它没有任何开销,并提供内存安全:

std::map<int, std::unique_ptr<int>> myMap;

void Store(int key, int* value)
{
    myMap[key] = std::unique_ptr<int>{value};
}

int main()
{
   Store(42, new int(42));
   Store(35, new int(35));
}

没有内存泄漏。 如果您可以访问 c++14,您甚至可以这样做:

std::map<int, std::unique_ptr<int>> myMap;

void Store(int key, std::unique_ptr<int> value)
{
    myMap[key] = std::move(value);
}

int main()
{
   Store(42, std::make_unique<int>(42));
   Store(35, std::make_unique<int>(35));
}

没有新的,没有删除。无内存泄漏且安全。

【讨论】:

  • 这个答案在精神上很好,但有很多错误。第一个和第二个代码示例都无法编译。首先,即使没有std::make_unique,好的做法是立即实例化std::unique_ptr,或者只是编写自己的make_unique 函数。 Store 不能取 int* 而是 std::unique_ptr&lt;int&gt;,然后必须使用 std::move(value) 将值插入到映射中。
【解决方案4】:

为什么不使用 map::insert

auto res = myMap.insert(std::make_pair(key,value));
if ( ! res.second ) {
    delete res.first->second;
    res.first->second = value;
} 

【讨论】:

  • 这个更短,我喜欢。
猜你喜欢
  • 1970-01-01
  • 2014-08-18
  • 1970-01-01
  • 2012-01-12
  • 2010-11-25
  • 2013-07-29
  • 2017-07-29
  • 2012-03-26
相关资源
最近更新 更多