【问题标题】:Avoid memory leak in C++ class避免 C++ 类中的内存泄漏
【发布时间】:2012-09-23 02:00:09
【问题描述】:

我已经使用以下头文件定义了一个 C++ 类:

class EarleyParser
{

    public:

        EarleyParser();
        virtual ~EarleyParser();

        void initialize( string filePath, bool probabilityParse );

    private:

        bool probabilityParser;

        typedef unordered_map< string, list<Production>* > productionHashTable;
        productionHashTable earlyHashTable;

};

如你所见,该类的成员元素是 unordered_map,其关键元素是字符串,内容元素是指向另一个名为 Production 的类的对象列表的指针(不要介意,它可以是任何东西)。

我的问题是我是否应该将它留给默认析构函数来释放分配的内存,或者我是否应该手动检查哈希表并删除它的所有元素。

在第二种情况下,程序是什么?为每个元素调用这个可以吗?

EarleyParser::productionHashTable::const_iterator got = this->earlyHashTable.find( "key" );
delete[] got->second;

【问题讨论】:

  • 如果这个对象拥有这个列表,那么你应该在那里删除它。使用共享指针而不是指向列表的原始指针 - 然后它将自行删除。
  • 如果我应该把它留给默认构造函数来释放分配的内存 - 你的意思是析构函数吗?
  • @Component10 - 当然...感谢您指出...
  • @Wojciech - 你能再具体一点吗?谢谢...

标签: c++ pointers memory-leaks unordered-map delete-operator


【解决方案1】:

您需要明确谁拥有EarlyParser 拥有的list&lt;Production&gt; 对象。如果EarlyParser 拥有它们,那么您需要释放这些资源。您可以通过遍历列表并在每个取消引用的迭代器上调用 delete 来完成此操作(not delete[])。或者您可以改为存储unique_ptr&lt;list&lt;Production&gt;&gt;。另一方面,最简单的解决方案是存储list&lt;Production&gt;,除非您确实有非常充分的理由存储指针。

【讨论】:

  • 从容器的角度来看,将整个列表存储为第二个元素或指向列表的指针有什么区别?有吗?thks...
  • @Matteo 第一个区别是您不必担心内存管理。第二个是如果你做复制容器之类的事情,所有的列表都会被复制,这不仅仅是复制指针。此外,需要复制元素的容器操作可能更昂贵。在 C++11 中,这是通过移动语义完成的,非常便宜。
  • 好的,我明白了。因此,由于我不希望复制容器,我可能应该简单地将列表存储为第二个元素。这样我也避免了内存泄漏问题,因为使用标准析构函数销毁 EarleyParser 类将调用 map 的标准析构函数(这将破坏列表)。我做对了吗?很多...
【解决方案2】:

如果您要在地图中存储指向任何内容的指针,则必须手动浏览地图并删除每个指针。通常在一个类中你会坚持使用 RAII(资源获取即初始化)并在 constructor 中构造东西并在 destructor

中销毁
 for (;;)
     delete map->second; //it's not an array of lists 

但是指向容器的指针不是一个好主意。为什么需要指针?您想使用指向list 的指针解决什么问题?

使用像std::unique_ptr 这样的智能指针比原始指针更好。原始指针应该是最后的手段,而不是当你想不出更好的东西时首先抓住的东西。

【讨论】:

  • 我的unordered_map 会发生碰撞,所以我想要一种方法来访问所有碰撞的元素。我认为将指针存储为第二个元素比放置整个列表更好。有没有更好的方法?
  • 如果要使用指针,请使用std::unique_ptrstd::shared_ptr,但不要使用原始指针。
  • 在下面的一些答案中,他们建议将整个列表存储为unordered_map 的内容。您认为这会是更好的方法吗?
  • @Matteo:当您说会有冲突时,您是在谈论具有相同键的多个值,还是具有相同哈希的多个键?后者是术语“碰撞”的通常含义,由unordered_map 内部处理。如果您的意思是多个值可以与同一个键关联,那么您可以使用unordered_multimap
【解决方案3】:

使用:

typedef unordered_map< string, std::unique_ptr<list<Production> > > productionHashTable;

相反。那么你就不用担心管理内存了。

【讨论】:

  • 在您看来,使用unique_ptr 或将完整列表存储为地图的第二个元素之间更好的解决方案是什么?寻求帮助..
  • @Matteo 嗯......现在我想了想,我会选择这份清单。无论如何,unique_ptr 都会阻止复制。如果您需要复制很多内容,我会使用 shared_ptr 而不是列表本身。
【解决方案4】:

编译器合成析构函数不会删除您放入映射中的动态分配列表,因此您必须自己做。在这种情况下,您只需遍历您的地图并删除每个元素的 second 成员:

EarleyParser::~EarleyParser() {
  for ( productionHashTable::iterator i = earlyHashTable.begin(); i != earlyHashTable.end(); ++i )
    delete i->second;
}

更好的方法是将列表放在地图中,而不是指向列表的指针。在这种情况下,编译器会自动处理销毁,如下所示:

typedef unordered_map< string, list<Production> > productionHashTable;

【讨论】:

    【解决方案5】:

    Unordered_map's destructor实际上调用了它拥有的对象的析构函数,这意味着lists的析构函数将被调用。 The destructor of std::list有这个注释:

    注意,如果元素是指针,则指向的对象不会被销毁。

    所以这意味着,你必须自己清除那段记忆。是的,通过容器并删除元素 1by1 很好。正如其他回答者所提到的那样,持有这样的指针并不是一个好主意。

    【讨论】:

      【解决方案6】:

      由于您使用的是指向 std::list 的原始指针,因此您必须在地图的生命周期内或在其析构函数中清理 EarleyParser 对象时自行删除它。

      你可以在你的析构函数中使用这样的东西:

      for ( auto it = productionHashTable.begin();
            it != productionHashTable.end(); ++it )
      { 
          delete it->second;
      }
      productionHashTable.clear()
      

      请注意,最后一行并不是绝对必要的,因为它无论如何都会被清除,因为 EarleyParser 对象已被破坏,但显然您在删除它们后不能使用映射中的值!

      【讨论】:

      • 循环体错误:应该使用it迭代器;无论使用哪个迭代器,都应该取消引用;第二个是数据成员而不是成员函数。
      • @NicolaMusatti:谢谢,我最初是把它写成 while ( ! productionHashTable.empty() ) 循环 - 然后意识到我没有删除任何东西!
      猜你喜欢
      • 1970-01-01
      • 2010-10-31
      • 2014-07-31
      • 2018-04-08
      • 2013-06-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多