Утечка памяти при использовании std :: string при использовании std :: list

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

#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;
    }

}

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

17
задан tshepang 23 May 2014 в 09:00
поделиться

5 ответов

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

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

30
ответ дан 30 November 2019 в 10:29
поделиться

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


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

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

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

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

У std::auto_ptr строгое владение - если указатель "копируется", то оригинал обнуляется и владение передается - существует только один правильный auto_ptr на объект. Объект, на который указывают, удаляется всякий раз, когда умный указатель с правом собственности выходит из области видимости.

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

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

2
ответ дан 30 November 2019 в 10:29
поделиться

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

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

13
ответ дан 30 November 2019 в 10:29
поделиться
    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.

2
ответ дан 30 November 2019 в 10:29
поделиться

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

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 , или загляните в библиотеку контейнеров указателей Boost .

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

sl->push_back(Line());

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

7
ответ дан 30 November 2019 в 10:29
поделиться
Другие вопросы по тегам:

Похожие вопросы: