Вызывает ли std:: list:: remove метод деструктор вызова каждого удаленного элемента?
std::list<Node *> lst;
//....
Node * node = /* get from somewhere pointer on my node */;
lst.remove(node);
Есть ли std:: list:: remove метод call destructor (и свободная память) каждого удаленного элемента? Если да, то как я могу избежать этого?
Ответы
Ответ 1
Да, удаление Foo*
из контейнера уничтожает Foo*
, но не освобождает Foo
. Уничтожение необработанного указателя всегда не работает. Это не может быть иначе! Позвольте мне дать вам несколько причин.
Класс хранения
Удаление указателя имеет смысл только в том случае, если указатель фактически был распределен динамически, но как может среда выполнения знать, действительно ли это так, когда переменная указателя уничтожена? Указатели также могут указывать на статические и автоматические переменные и удалять один из этих значений undefined.
{
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.)
}
Висячие указатели
Невозможно выяснить, уже ли был выпущен плакат в прошлом. При удалении одного и того же указателя дважды выполняется undefined поведение. (После первого удаления он становится свисающим указателем.)
{
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.)
}
Неинициализированные указатели
Также невозможно определить, была ли инициализирована переменная указателя. Угадайте, что происходит, когда вы пытаетесь удалить такой указатель? Еще раз, ответ undefined поведение.
{
Foo* p;
// Has p been properly initialized?
// (The answer is NO, but the runtime doesn't know that.)
}
Динамические массивы
Система типов не различает указатель на один объект (Foo*
) и указатель на первый элемент массива объектов (также Foo*
). Когда переменная указателя уничтожается, среда выполнения не может определить, следует ли освобождать запятую через delete
или через delete[]
. Выпуск из неправильной формы вызывает undefined поведение.
{
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.)
}
Резюме
Поскольку время выполнения не может сделать ничего разумного с помощью pointee, уничтожение переменной указателя всегда является no-op. Нечего делать, безусловно, лучше, чем вызывать поведение undefined из-за неосведомленного предположения: -)
Совет
Вместо необработанных указателей подумайте о том, чтобы использовать интеллектуальные указатели как тип значения вашего контейнера, потому что они несут ответственность за освобождение пункта, когда он больше не нужен. В зависимости от ваших потребностей используйте std::shared_ptr<Foo>
или std::unique_ptr<Foo>
. Если ваш компилятор еще не поддерживает С++ 0x, используйте boost::shared_ptr<Foo>
.
Никогда, повторяю, НИКОГДА не используйте std::auto_ptr<Foo>
как тип значения контейнера.
Ответ 2
Он вызывает деструктор каждого из элементов в объекте list
- но не Node
. Его а Node*
.
Поэтому он не удаляет указатели Node
.
Это имеет смысл?
Ответ 3
Он вызывает деструктор данных в списке. Это означает, что std::list<T>::remove
вызовет деструктор T
(что необходимо, когда T
что-то вроде std::vector
).
В вашем случае это вызовет деструктор Node*
, который не является оператором. Он не вызывает деструктор node
.
Ответ 4
Да, хотя в этом случае Node * не имеет деструктора. Однако, в зависимости от его внутренних компонентов, различные значения Node * либо удаляются, либо уничтожаются по правилам определения области видимости. Если Node *, где некоторый нефакторный тип, вызывается деструктор.
Является ли деструктор вызываемым на Node? Нет, но 'Node' не является типом элемента в списке.
Что касается вашего другого вопроса, вы не можете. Стандартный контейнер списка (фактически ВСЕ стандартные контейнеры) принимает право собственности на свой контент и очищает его. Если вы не хотите, чтобы это произошло, стандартные контейнеры не являются хорошим выбором.
Ответ 5
Поскольку вы помещаете указатели в std::list
, деструкторы не вызывают объекты с указателем на Node
.
Если вы хотите хранить кучи выделенных объектов в контейнерах STL и уничтожить их при удалении, оберните их в интеллектуальный указатель, например boost::shared_ptr
Ответ 6
Лучший способ понять - проверить каждую форму и наблюдать результаты. Чтобы умело использовать объекты контейнера со своими собственными пользовательскими объектами, вам нужно хорошо понимать поведение.
Короче говоря, для типа Node*
не вызывается деконструктор и не вызывается delete/free; однако для типа Node
деконструктор будет вызываться, в то время как рассмотрение delete/free является деталью реализации списка. Смысл, это зависит от того, используется ли в реализации списка new/malloc.
В случае unique_ptr<Node>
вызывается деконструктор, и вызов delete/free произойдет, так как вам нужно присвоить ему что-то выделенное 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;
}
Обратите внимание на адреса памяти. Вы можете указать, какие из них указывают в стек и которые указывают на кучу диапазонами.