Утечка памяти при использовании std :: string при использовании std :: list - PullRequest
17 голосов
/ 07 августа 2010

Я работаю с std::list<std::string> в моем текущем проекте.Но где-то с этим связана утечка памяти.Итак, я протестировал проблемный код отдельно:

#include <iostream>
#include <string>
#include <list>

class Line {
public:
    Line();
    ~Line();
    std::string* mString;
};

Line::Line() {
    mString = new std::string("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
}

Line::~Line() {
    //mString->clear(); // should not be neccessary
    delete mString;
}

int main(int argc, char** argv)
{
    // no memory leak
    while (1==1) {
        std::string *test = new std::string("XXXXXXXXXXXXXXXXXXXXXXXX");
        delete test;
    }

    // LEAK!
    // This causes a memory overflow, because the string thats added
    // to the list is not deleted when the list is deleted.
    while (1==1) {
        std::list<std::string> *sl = new std::list<std::string>;
        std::string *s = new std::string("XXXXXXXXXXXXXXXXXXXXXXX");
        sl->push_back(*s);
        //sl->pop_back(); //doesn't delete the string?- just the pointer
        delete sl;
    }

    // LEAK!
    // Here the string IS deleted, but the memory does still fill up
    // but slower
    while (1==1) {
        std::list<Line> *sl = new std::list<Line>;
        Line *s = new Line();
        sl->push_back(*s);
        //sl->pop_back(); //does delete the Line-Element
        sl->clear();
        delete sl;
    }
    return 0;

    // this does not cause any noticable memory leak
    while (1==1) {
        std::list<int> *sl = new std::list<int>;
        int i = 0xFFFF;
        sl->push_back(i);
        sl->clear();
        delete sl;
    }
    return 0;

    // This does not cause any overflow or leak
    while (1==1) {
        int *i;
        i= new int [9999];
        delete[] i;
    }

}

Почему мой список строк вызывает утечку памяти?Разве удаление списка не должно вызывать деструкторы для каждой содержащейся строки?

Ответы [ 5 ]

30 голосов
/ 07 августа 2010

В первом случае класс list не знает, что вы выделили строку с new, и не может удалить ее.В частности, список содержит только копию строки, которую вы передали.

Аналогично, во втором случае вы никогда не освобождаете объект строки s и, таким образом, вы теряете память.Причина, по которой внутренняя строка удаляется, заключается в том, что вы неправильно реализовали конструктор копирования.Таким образом, если вы сделаете копию объекта Line, оба они будут ссылаться на один и тот же строковый указатель, и если вы попытаетесь удалить оба из них, у вас возникнут проблемы.

13 голосов
/ 07 августа 2010

Вашему классу Line требуется оператор копирования и оператор присваивания, которые правильно обрабатывают указатель строки.

В качестве альтернативы, просто используйте член std::string вместо указателя и позвольте классу string обрабатывать память (для этого она и нужна).

7 голосов
/ 07 августа 2010

Вот ваша утечка:

while (1==1) {
    std::list<Line> *sl = new std::list<Line>;
    Line *s = new Line();
    sl->push_back(*s);
    //sl->pop_back(); //does delete the Line-Element
    sl->clear();
    delete sl;
}

Коллекции STL хранят элементы по значению , выделяя и освобождая место для него. То, что вы выделили , вы должны выпустить явно. Просто добавьте delete s в конец цикла.

Если вам необходимо хранить указатели, рассмотрите возможность хранения управляемых указателей, таких как boost::shared_ptr, или посмотрите Библиотека контейнеров повышающих указателей .

На втором этапе вам вообще не нужно выделять Line в куче. Просто измените его на:

sl->push_back(Line());

И, как уже отмечалось, убедитесь, что элементы указателя Line правильно управляются в конструкторе копирования, назначении копирования и деструкторе.

2 голосов
/ 07 августа 2010

Другие обращались конкретно к тому, почему у вас утечка - удаление списка указателей не удаляет объекты, на которые указывают, и не должно быть простым указателем, который не указывает, была ли это единственная ссылка на этот объект), ноЕсть и другие способы, чем необходимость повторять список при удалении для удаления указателей.


Поскольку в приведенном здесь примере нет никакой причины вообще использовать указатели на что-либо, так как вы 'использовать их, когда они входят в область действия, и отбрасывать их, когда они выходят из области действия, - вместо этого просто создайте все в стеке, и компилятор должным образом утилизирует все при выходе из области действия.Например,

while (1==1) {
    std::list<std::string> sl;
    std::string s = std::string("XXXXXXXXXXXXXXXXXXXXXXX");
    sl.push_back(s);
}

Если вам нужно поведение указателя (чтобы избежать дублирования объектов, с которыми связаны многие вещи и т. Д. И т. Д.), Вам следует взглянуть на умные указатели, так какони устранят многие подводные камни, поскольку они могут автоматически обрабатывать подсчет ссылок и семантику, которая вам требуется.(В частности, посмотрите на интеллектуальные указатели повышения )

Существует много типов интеллектуальных указателей, которые вы можете использовать в зависимости от конкретной потребности и семантики владения для представления.

Std :: auto_ptr имеет строгое владение - если указатель «скопирован», оригинал обнуляется и право собственности передается - всегда существует только один действительный auto_ptr для объекта.Указанный объект удаляется всякий раз, когда интеллектуальный указатель с владельцем выходит из области видимости.

Кроме того, улучшаются общие и слабые указатели, используя подсчет ссылок, чтобы узнать, когда следует освобождать объект, на который указывает указатель.При использовании общих указателей каждая копия указателя увеличивает количество ссылок, и объект, на который указывает указатель, удаляется всякий раз, когда все общие указатели выходят за пределы области видимости.Слабый указатель указывает на объект, управляемый общим указателем, но не увеличивает счетчик ссылок, если все родительские общие указатели будут удалены, при попытке разыменования слабый указатель сгенерирует легко отслеживаемое исключение.

Очевидно, существуетгораздо больше, чем умные указатели, но я настоятельно рекомендую взглянуть на них как на решение, помогающее управлять вашей памятью.

2 голосов
/ 07 августа 2010
    std::list<Line> *sl = new std::list<Line>;
    Line *s = new Line();
    sl->push_back(*s);
    //sl->pop_back(); //does delete the Line-Element
    sl->clear();
    delete sl;

Вы забыли удалить s. Вы обновили его, вы должны удалить его. Поскольку вы копируете объекты (помещая их в список) при управлении памятью в своем классе Line, вы также должны предоставить конструктор копирования и оператор присваивания для вашего класса Line.

...