我有代码:
std::list<Node *> lst;
//....
Node * node = /* get from somewhere pointer on my node */;
lst.remove(node);
该std::list::remove
方法是否调用每个已删除元素的析构函数(和空闲内存)?如果是这样,我该如何避免它?
我有代码:
std::list<Node *> lst;
//....
Node * node = /* get from somewhere pointer on my node */;
lst.remove(node);
该std::list::remove
方法是否调用每个已删除元素的析构函数(和空闲内存)?如果是这样,我该如何避免它?
是的,Foo*
从容器中删除 a 会破坏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*
的指针 () 和指向对象数组的第一个元素的指针 (also 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>
容器的值类型。
它调用中每个项目的析构函数list
-- 但这不是一个Node
对象。它的一个Node*
。
所以它不会删除Node
指针。
那有意义吗?
它确实调用了列表中数据的析构函数。这意味着,std::list<T>::remove
将调用的析构函数(在类似的情况下T
是必要的)。T
std::vector
在您的情况下,它将调用 的析构函数Node*
,这是一个无操作。它不调用node
.
是的,尽管在这种情况下,Node* 没有析构函数。但是,根据其内部结构,不同的 Node* 值会被范围规则删除或销毁。如果 Node* 中有一些非基本类型,则将调用析构函数。
析构函数是否在节点上调用?不,但“节点”不是列表中的元素类型。
至于你的另一个问题,你不能。标准列表容器(实际上是所有标准容器)采用其内容的所有权并将其清理。如果您不希望这种情况发生,那么标准容器不是一个好的选择。
由于您将指针放入 a std::list
,因此不会在指向的Node
对象上调用析构函数。
如果您想将堆分配的对象存储在 STL 容器中并在删除时销毁它们,请将它们包装在一个智能指针中,例如boost::shared_ptr
最好的理解方法是测试每个表格并观察结果。要巧妙地将容器对象与您自己的自定义对象一起使用,您需要对行为有很好的理解。
简而言之,对于Node*
既不调用解构函数也不调用 delete/free 的类型;但是,对于该类型Node
,将调用解构器,同时考虑删除/释放是列表的实现细节。意思是,这取决于列表实现是否使用了 new/malloc。
在 a 的情况下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;
}
请特别注意内存地址。您可以通过范围判断哪些指向堆栈,哪些指向堆。