Есть ли деструктор вызова метода std :: list :: remove для каждого удаленного элемента?

У меня есть код:

std::list<Node *> lst;
//....
Node * node = /* get from somewhere pointer on my node */;
lst.remove(node);

Вызывает ли метод std::list::remove деструктор (и свободную память) каждого удаленного элемента? Если да, то как я могу этого избежать?


person Siarhei Fedartsou    schedule 23.11.2010    source источник
comment
Почему вы вообще храните указатели?   -  person GManNickG    schedule 23.11.2010


Ответы (6)


Да, удаление Foo* из контейнера уничтожает Foo*, но не освобождает Foo. Уничтожение необработанного указателя всегда является запретом. Иначе и быть не может! Позвольте мне назвать вам несколько причин, почему.

Класс хранения

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

{
    Foo x;
    Foo* p = &x;

    Foo* q = new Foo;

    // Has *q been allocated dynamically?
    // (The answer is YES, but the runtime doesn't know that.)

    // Has *p been allocated dynamically?
    // (The answer is NO, but the runtime doesn't know that.)
}

Свисающие указатели

Невозможно выяснить, выпускался ли указатель в прошлом. Удаление одного и того же указателя дважды приводит к неопределенному поведению. (После первого удаления он становится свисающим указателем.)

{
    Foo* p = new Foo;

    Foo* q = p;

    // Has *q already been released?
    // (The answer is NO, but the runtime doesn't know that.)

    // (...suppose that pointees WOULD be automatically released...)

    // Has *p already been released?
    // (The answer WOULD now be YES, but the runtime doesn't know that.)
}

Неинициализированные указатели

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

    {
        Foo* p;

        // Has p been properly initialized?
        // (The answer is NO, but the runtime doesn't know that.)
    }

Динамические массивы

Система типов не делает различий между указателем на отдельный объект (Foo*) и указателем на первый элемент массива объектов (также Foo*). Когда переменная-указатель уничтожается, среда выполнения не может понять, следует ли освободить указатель через delete или через delete[]. Освобождение через неправильную форму вызывает неопределенное поведение.

{
    Foo* p = new Foo;

    Foo* q = new Foo[100];

    // What should I do, delete q or delete[] q?
    // (The answer is delete[] q, but the runtime doesn't know that.)

    // What should I do, delete p or delete[] p?
    // (The answer is delete p, but the runtime doesn't know that.)
}

Резюме

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

Совет

Вместо необработанных указателей рассмотрите возможность использования интеллектуальных указателей в качестве типа значения вашего контейнера, потому что они берут на себя ответственность за освобождение указателя, когда он больше не нужен. В зависимости от ваших потребностей используйте std::shared_ptr<Foo> или std::unique_ptr<Foo>. Если ваш компилятор еще не поддерживает C ++ 0x, используйте boost::shared_ptr<Foo>.

Никогда, повторяю, НИКОГДА не используйте std::auto_ptr<Foo> в качестве типа значения контейнера.

person fredoverflow    schedule 23.11.2010

Он вызывает деструктор каждого элемента в list, но это не объект Node. Это Node*.

Таким образом, он не удаляет указатели Node.

Имеет ли это смысл?

person John Dibling    schedule 23.11.2010
comment
Но у меня возникает ошибка сегментации, если я пытаюсь удалить узел после вызова remove. :( Почему так случилось? - person Siarhei Fedartsou; 23.11.2010
comment
@JohnDibling Понятно, что объект не будет удален, когда его необработанный указатель будет удален из контейнера. Но вызовет ли он деструкцию или объект? Я думаю, что так быть не должно. Но в документации говорится, что деструктор вызывается для удаляемого элемента карты. - person PHcoDer; 11.04.2017

Он вызывает деструктор данных в списке. Это означает, что std::list<T>::remove вызовет деструктор T (что необходимо, когда T является чем-то вроде std::vector).

В вашем случае он вызовет деструктор Node*, который не работает. Он не вызывает деструктор node.

person jpalecek    schedule 23.11.2010
comment
да. Для большей ясности показанный пример кода не вызывает Node::~Node и не delete указатель node. (Хотя, если у вас есть iterator для этого места в списке, теперь он недействителен.) - person aschepler; 23.11.2010

Да, но в этом случае Node * не имеет деструктора. Однако, в зависимости от его внутреннего устройства, различные значения Node * либо удаляются, либо уничтожаются правилами области видимости. Если Node * имеет некоторый нефундаментальный тип, будет вызван деструктор.

Вызывается ли деструктор на узле? Нет, но «Узел» не является типом элемента в списке.

Что касается вашего другого вопроса, вы не можете. Стандартный контейнер списка (фактически ВСЕ стандартные контейнеры) принимает право владения своим содержимым и очищает его. Если вы этого не хотите, стандартные контейнеры - не лучший выбор.

person Edward Strange    schedule 23.11.2010

Поскольку вы помещаете указатели в std::list, деструкторы не вызываются для объектов, на которые указывает Node.

Если вы хотите сохранить объекты, выделенные кучей, в контейнерах STL и уничтожить их при удалении, оберните их в интеллектуальный указатель, например boost::shared_ptr

person wkl    schedule 23.11.2010

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

Короче говоря, для типа Node* не вызывается ни деконструктор, ни удаление / освобождение; однако для типа Node деконструктор будет вызываться, в то время как рассмотрение удаления / освобождения является деталью реализации списка. Это означает, что это зависит от того, использовала ли реализация списка new / malloc.

В случае unique_ptr<Node> вызывается деконструктор, и произойдет вызов удаления / освобождения, поскольку вы должны были передать ему что-то, выделенное new.

#include <iostream>
#include <list>
#include <memory>

using namespace std;

void* operator new(size_t size) {
    cout << "new operator with size " << size << endl;
    return malloc(size);
}

void operator delete(void *ptr) {
    cout << "delete operator for " << ptr << endl;
    free(ptr);
}

class Apple {
public:
    int id;

    Apple() : id(0) { cout << "apple " << this << ":" << this->id << " constructed" << endl; } 
    Apple(int id) : id(id) { cout << "apple " << this << ":" << this->id << " constructed" << endl; }
    ~Apple() { cout << "apple " << this << ":" << this->id << " deconstructed" << endl; }

    bool operator==(const Apple &right) {
        return this->id == right.id;
    }

    static void* operator new(size_t size) {
        cout << "new was called for Apple" << endl;
        return malloc(size);
    }

    static void operator delete(void *ptr) {
        cout << "delete was called for Apple" << endl;
        free(ptr);
    }
    /*
        The compiler generates one of these and simply assignments
        member variable. Think memcpy. It can be disabled by uncommenting
        the below requiring the usage of std::move or one can be implemented.
    */
    //Apple& operator=(const Apple &from) = delete;
};

int main() {
    list<Apple*> a = list<Apple*>();

    /* deconstructor not called */
    /* memory not released using delete */
    cout << "test 1" << endl;
    a.push_back(new Apple());
    a.pop_back();

    /* deconstructor not called */
    /* memory not released using delete */
    cout << "test 2" << endl;
    Apple *b = new Apple();
    a.push_back(b);
    a.remove(b);
    cout << "list size is now " << a.size() << endl;

    list<Apple> c = list<Apple>();      
    cout << "test 3" << endl;
    c.push_back(Apple(1)); /* deconstructed after copy by value (memcpy like) */
    c.push_back(Apple(2)); /* deconstructed after copy by value (memcpy like) */

    /*
       the list implementation will call new... but not
       call constructor when Apple(2) is pushed; however,
       delete will be called; since it was copied by value
       in the last push_back call

       double deconstructor on object with same data
    */
    c.pop_back();

    Apple z(10);

    /* will remove nothing */
    c.remove(z);

    cout << "test 4" << endl;

    /* Apple(5) will never deconstruct. It was literally overwritten by Apple(1). */
    /* Think memcpy... but not exactly. */
    z = Apple(1);

    /* will remove by matching using the operator== of Apple or default operator== */
    c.remove(z);

    cout << "test 5" << endl;
    list<unique_ptr<Apple>> d = list<unique_ptr<Apple>>();
    d.push_back(unique_ptr<Apple>(new Apple()));
    d.pop_back();

    /* z deconstructs */
    return 0;
}

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

person kmcguire    schedule 17.08.2016