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

Я работаю с 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;
    }

}

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

Ответы

Ответ 1

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

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

Ответ 2

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

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

Ответ 3

Здесь ваша утечка:

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 правильно управляются в конструкторе-копии, присваивании копии и деструкторе.

Ответ 4

    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.

Ответ 5

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


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

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

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

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

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

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

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