std::list::remove 方法是不是调用每个已删除元素的析构函数?

Posted

技术标签:

【中文标题】std::list::remove 方法是不是调用每个已删除元素的析构函数?【英文标题】:Does std::list::remove method call destructor of each removed element?std::list::remove 方法是否调用每个已删除元素的析构函数? 【发布时间】:2011-05-14 17:58:05 【问题描述】:

我有代码:

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

std::list::remove 方法是否调用每个已删除元素的析构函数(和释放内存)?如果是这样,我该如何避免?

【问题讨论】:

为什么要存储指针? 【参考方案1】:

由于您将指针放入std::list,因此不会对指向的Node 对象调用析构函数。

如果您想将堆分配的对象存储在 STL 容器中并在删除时将其销毁,请将它们包装在一个智能指针中,例如 boost::shared_ptr

【讨论】:

【参考方案2】:

它调用list 中每个项目的析构函数——但这不是Node 对象。它是Node*

所以它不会删除Node 指针。

这有意义吗?

【讨论】:

但如果我在删除调用后尝试删除节点,则会出现“分段错误”。 :(为什么会这样? @JohnDibling 很明显,当从容器中删除其原始指针时,该对象不会被删除。但它会调用对象的析构或吗?我认为情况不应该如此。但是文档说要删除地图元素时调用析构函数。【参考方案3】:

它确实调用了列表中数据的析构函数。这意味着,std::list&lt;T&gt;::remove 将调用T 的析构函数(当T 类似于std::vector 时这是必要的)。

在您的情况下,它将调用Node* 的析构函数,这是一个无操作。它不会调用node 的析构函数。

【讨论】:

是的。为了更加清楚,显示的代码示例没有调用Node::~Node,也没有调用deletenode 指针。 (虽然如果您在列表中的那个位置有一个iterator,它现在是无效的。)【参考方案4】:

是的,尽管在这种情况下,Node* 没有析构函数。但是,根据其内部结构,不同的 Node* 值会被范围规则删除或销毁。如果 Node* 中有一些非基本类型,则会调用析构函数。

是否在节点上调用了析构函数?不,但“节点”不是列表中的元素类型。

至于你的另一个问题,你不能。标准列表容器(实际上是所有标准容器)采用其内容的所有权并将其清理。如果您不希望这种情况发生,那么标准容器不是一个好的选择。

【讨论】:

【参考方案5】:

是的,从容器中删除 Foo* 会破坏 Foo*,但不会释放 Foo。销毁原始指针总是是无操作的。不能有别的办法!让我给你几个理由。

存储类

删除指针只有在指针对象实际上是动态分配的情况下才有意义,但是运行时如何知道指针变量被销毁时是否是这种情况?指针也可以指向静态和自动变量,删除其中一个会产生undefined behavior。


    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 behavior。 (第一次删除后它变成了一个悬空指针。)


    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 behavior。

    
        Foo* p;

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

动态数组

类型系统不区分指向单个对象的指针 (Foo*) 和指向对象数组的第一个元素的指针 (也是 Foo*)。当指针变量被销毁时,运行时可能无法确定是通过delete 还是通过delete[] 释放指针。通过错误的形式释放调用undefined behavior。


    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&lt;Foo&gt;std::unique_ptr&lt;Foo&gt; 。如果您的编译器还不支持 C++0x,请使用 boost::shared_ptr&lt;Foo&gt;

Never,我再说一遍,永远不会使用 std::auto_ptr&lt;Foo&gt; 作为容器的值类型。

【讨论】:

【参考方案6】:

最好的理解方法是测试每个表格并观察结果。要熟练地将容器对象与您自己的自定义对象一起使用,您需要对行为有很好的了解。

简而言之,对于Node* 类型,既没有调用解构函数,也没有调用delete/free;但是,对于Node 类型,将调用解构器,而考虑删除/释放是列表的实现细节。意思是,这取决于列表实现是否使用 new/malloc。

unique_ptr&lt;Node&gt; 的情况下,将调用解构函数并调用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;

注意内存地址。您可以通过范围来分辨哪些指向堆栈,哪些指向堆。

【讨论】:

以上是关于std::list::remove 方法是不是调用每个已删除元素的析构函数?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Boost lambda 将 remove_if 与 UUID 比较应用?

使用 Moq 确定是不是调用了方法

从另一个同步方法调用同步方法是不是安全?

c# 有调用一个类的静态方法,是不是执行这个类的构造函数

在单元测试中同步调用异步方法是不是不正确?

如果一个同步方法调用另一个非同步方法,非同步方法是不是有锁