内存泄漏 - STL集合

7
我正在尝试解决我的内存泄漏问题(非常严重)。我对STL不熟悉。我有一个类库,其中包含3个集合。我还在类库类中使用new创建大量内存,用于将信息添加到这些集合中...
我需要释放这些集合吗?如果是,应该如何释放?
以下是library.h的代码:
    #pragma once

#include <ostream>
#include <map>
#include <set>
#include <string>
#include "Item.h"

using namespace std;

typedef set<Item*>              ItemSet;
typedef map<string,Item*>       ItemMap;
typedef map<string,ItemSet*>    ItemSetMap;

class Library
{

public:
    // general functions

    void addKeywordForItem(const Item* const item, const string& keyword);
    const ItemSet* itemsForKeyword(const string& keyword) const;
    void printItem(ostream& out, const Item* const item) const;

    // book-related functions

    const Item* addBook(const string& title, const string& author, int const nPages);
    const ItemSet* booksByAuthor(const string& author) const;
    const ItemSet* books() const;

    // music-related functions

    const Item* addMusicCD(const string& title, const string& band, const int nSongs);
    void addBandMember(const Item* const musicCD, const string& member);
    const ItemSet* musicByBand(const string& band) const;
    const ItemSet* musicByMusician(const string& musician) const;
    const ItemSet* musicCDs() const;

    // movie-related functions

    const Item* addMovieDVD(const string& title, const string& director, const int nScenes);
    void addCastMember(const Item* const movie, const string& member);
    const ItemSet* moviesByDirector(const string& director) const;
    const ItemSet* moviesByActor(const string& actor) const;
    const ItemSet* movies() const;
    ~Library();
};

我不确定如何编写析构函数?

Library::~Library()
{


}

另外,我是否正确地释放了字符串集?

    #ifndef CD_H
#define CD_H
#pragma once
#include "item.h"
#include <set>


typedef set<string> StringSet;


class CD : public Item
{
public:

    CD(const string& theTitle, const string& theBand, const int snumber);
    void addBandMember(const string& member);
    const int getNumber() const;
    const StringSet* getMusician() const;
    const string getBand() const;
    virtual void print(ostream& out) const;
    string printmusicians(const StringSet* musicians) const;

    ~CD();


private:

    string band;
    StringSet* music;

    string title;
    int number;

};

ostream& operator<<(ostream& out, const CD* cd);

#endif

cd.cpp

    #include "CD.h"

using namespace std;

CD::CD(const string& theTitle, const string& theBand, const int snumber)
: Item(theTitle), band(theBand),number(snumber), music(new StringSet)
{



}

CD::~CD()
{

    delete []music;

}

在我创建的库类中,我会产生很多的内存,但是析构函数不会清理它吗? 例如:

    const Item* Library::addBook(const string& title, const string& author, const int nPages)
{

    ItemSet* obj = new ItemSet();
    Book* item = new Book(title,author,nPages);
    allBooks.insert(item); // add to set of all books
    obj->insert(item);

注意:我没有拷贝构造函数。我不确定是否需要一个,也不知道如何添加一个。我认为我的析构函数也没有被调用。

简短回答:在您的代码中搜索每个 * 字符的实例,并将其删除。在 C++ 中,您几乎不需要使用指针。每次使用指针时,都会导致内存泄漏的风险。 - jalf
4
即使你使用标准类型,在C++中仍然有很多时候需要使用指针。 - Xorlev
1
是的,可能也有例外,但你看过那位提问者的代码了吗?他用指针来处理 所有 的东西。容器存储指针,每个对象都通过指针引用,所有东西都是动态分配的,一切必须被清理干净。难道你认为告诉他不应该对所有这些东西使用指针是 "多虑" 吗? - jalf
我的过去评论被删除并不是因为不正确,而是因为在这里过于冗长和不适当。 - user180247
我的理解是,在容器类中使用智能指针可能会很危险。如果容器需要重新定位对象,就会出现问题。https://dev59.com/AG455IYBdhLWcg3wCPjh - Paul
显示剩余3条评论
7个回答

4

STL容器不是用来存储指针的。

可以看一下boost指针容器。这些容器是专门设计用来存储指针的。

#include <boost/ptr_container/ptr_set.hpp>
#include <boost/ptr_container/ptr_map.hpp>

http://www.boost.org/doc/libs/1_42_0/libs/ptr_container/doc/ptr_set.html

这些容器持有指针并在容器超出范围时删除它们。但是这些容器的美妙之处在于,您可以通过引用访问对象,因此所有标准算法都可以无需任何特殊适配器即可工作。

typedef boost::ptr_set<Item>              ItemSet;
typedef boost::ptr_map<string,Item>       ItemMap;
typedef boost::ptr_map<string,ItemSet>    ItemSetMap;

PS. 很难准确地说,但看起来你的接口返回了过多的指针。在C++中,实际上很少返回指针(或传递指针)。你的接口通常应该采用对象/引用或智能指针(通常按照这个顺序,但这取决于情况)。

使用指针应该是最后的选择,因为没有明确的对象所有者指示,因此清理变得困难(从而导致大量内存泄漏)。


3
你需要为集合中的每个元素释放内存。容器不会为你做这件事,也不应该这样做,因为它无法知道它是否拥有这些数据--它只能持有指向其他对象所拥有的指针。

这是一个通用的释放函数,可以释放任何STL容器。

template <typename T>
void deallocate_container(T& c)
{
  for (typename T::iterator i = c.begin(); i != c.end(); ++i)
    delete *i;
}

// Usage
set<SomeType*> my_set;
deallocate_container(my_set);
my_set.clear();

但是你需要运行时类型信息来实现,哈! - pajton
为什么你需要在这里使用RTTI? - Peter Alexander
它可能需要类具有虚析构函数。大多数类都需要虚析构函数。问题是类的属性,而不是这个deallocate_container函数。但有一个问题 - 它是否适用于std :: map,它对于每个项都有键和数据,其中一个或两个都可能是指针? - user180247
是的,它需要一个虚析构函数,但这与使用此函数无关。是的,它不能与映射一起使用,因为迭代器指向对。对于映射,您可以在键和/或值中动态分配内存,因此您需要特别编写这些版本。 - Peter Alexander

1

嗯,这可能是一个愚蠢的评论,但你真的需要将所有东西都堆分配(即使用指针和new吗?)

你不能只使用普通实例吗? RAII允许更容易的代码和无内存泄漏。

例如:

using namespace std;

typedef set<Item>              ItemSet;
typedef map<string,Item>       ItemMap;
typedef map<string,ItemSet>    ItemSetMap;

class Library
{

public:
    // general functions

    void addKeywordForItem(const Item & item, const string& keyword);
    ItemSet itemsForKeyword(const string& keyword) const;
    void printItem(ostream& out, const Item & item) const;

    // book-related functions

    Item addBook(const string& title, const string& author, int nPages);
    ItemSet booksByAuthor(const string& author) const;
    ItemSet books() const;

    // music-related functions

    Item addMusicCD(const string& title, const string& band, int nSongs);
    void addBandMember(const Item & musicCD, const string& member);
    ItemSet musicByBand(const string& band) const;
    ItemSet musicByMusician(const string& musician) const;
    ItemSet musicCDs() const;

    // movie-related functions

    Item addMovieDVD(const string& title, const string& director, int nScenes);
    void addCastMember(const Item & movie, const string& member);
    ItemSet moviesByDirector(const string& director) const;
    ItemSet moviesByActor(const string& actor) const;
    ItemSet movies() const;
    ~Library();
};

采用这种方法,析构函数不需要执行任何操作,也不会出现内存泄漏。在大多数情况下,可以很容易地避免使用指针,而且绝对应该这样做!


1

看了你在其他问题中发布的代码(例如https://stackoverflow.com/questions/2376099/c-add-to-stl-set),你的项目存储在几个全局ItemSet对象中。这不是一个好的设计 - 它们真的应该是Library对象的一部分,因为它们在逻辑上属于那个对象。

修复内存泄漏的最佳方法不是处理原始指针 - 而是在集合中存储智能指针,或者像Martin York建议的那样使用Boost指针容器。另外,你的ItemSetMap对象应该包含Set对象而不是指针 - 没有任何理由在其中存储指针。

如果你确实必须存储指针,那么你的析构函数必须遍历每个集合以删除内容:

void Purge(ItemSet &set)
{
    for (ItemSet::iterator it = set.begin(); it != set.end(); ++it)
        delete *it;
    set.clear(); // since we won't actually be destroying the container
}

void Purge(ItemSetMap &map)
{
    for (ItemSetMap::iterator it = map.begin(); it != map.end(); ++it)
        delete it->second;
    map.clear();
}

Library::~Library()
{
    Purge(allBooks);
    Purge(allCDs);
    // and so on and so forth
}

但这确实不是你应该采取的方式,因为几乎每个回答你问题的人都指出了这一点。

至于 StringSet,你用普通的 new 创建它而不是用 new[],所以你必须用普通的 delete 而不是用 delete[] 删除它。或者,更好的方法是将 music 设为 StringSet 对象而不是指针,那么你根本不需要析构函数。再次强调,通过原始指针和手动使用 delete 进行内存管理容易出错,如果可能的话应该避免使用。


我还有一个typedef set<string> StringSet; StringSet* Moviecast; 我能直接使用delete Moviecast吗?还是必须迭代? - user249375
不需要迭代 set<string>,因为它包含对象并会自行释放它们。您只需要删除使用 new 分配的对象,并尽可能避免这样做。 - Mike Seymour
我在几乎所有的函数中都使用了new方法来添加信息到我的集合中,例如:ItemSet* obj = new ItemSet(); 那么我该如何删除它们呢?在离开函数之前我无法删除它们。- 希望您不介意,我使用了您的清除(purge)函数。 - user249375

1

我没有仔细查看你的所有代码,但从前几行来看,似乎你正在维护一些指针集合。每当你有一个STL容器,其中包含指针,并且你使用new将内容放入指针中时,必须使用delete来释放这些指针。STL并不会为您执行此操作。事实上,STL甚至不知道它们是指针。

另一个选择是根本不使用指针,只使用对象集合,而不使用new来创建它们。只需在堆栈上创建它们并将其复制到集合中即可。


0
在析构函数中,您需要迭代包含指针的STL集合并将它们删除。就像这样:
while (!collection.empty()) {
    collection_type::iterator it = collection.begin();
    your_class* p = *it;
    collection.erase(it);
    delete p;
}

0
正如其他人所指出的那样,您需要取消分配指针。 set 析构函数通常不会为您执行此操作。否则,如果您希望这样做,请使用 boost::scoped_ptrstd::tr1::shared_ptr,其中您可以指定自定义删除器来执行此任务。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接