何时调用C ++析构函数?

时间:2012-04-10 00:08:06

标签: c++ destructor

基本问题:程序何时在C ++中调用类的析构函数方法?我被告知只要一个物体超出范围或受到delete

的影响就会被调用

更具体的问题:

1)如果对象是通过指针创建的,并且该指针稍后被删除或者指定了一个新地址,那么它所指向的对象是否会调用它的析构函数(假设没有其他任何东西指向它)? / p>

2)继续问题1,什么定义了一个对象何时超出范围(不是关于对象何时离开给定的{block})。那么,换句话说,什么时候是一个在链表中调用对象的析构函数?

3)您是否想要手动调用析构函数?

10 个答案:

答案 0 :(得分:60)

  

1)如果对象是通过指针创建的,并且该指针稍后被删除或者指定了一个新地址,那么它所指向的对象是否会调用它的析构函数(假设没有其他任何东西指向它)? / p>

这取决于指针的类型。例如,智能指针通常在删除对象时删除它们。普通指针没有。当指针指向不同的对象时也是如此。一些智能指针会破坏旧对象,或者如果它没有更多引用就会销毁它。普通指针没有这样的智慧。它们只是持有一个地址,允许您通过专门的操作对它们指向的对象执行操作。

  

2)继续问题1,什么定义了一个对象何时超出范围(不是关于对象何时离开给定的{block})。那么,换句话说,什么时候是一个在链表中调用对象的析构函数?

这取决于链表的实现。典型的集合在销毁时会销毁所有包含的对象。

因此,链接的指针列表通常会破坏指针,但不会破坏它们指向的对象。 (这可能是正确的。它们可能是其他指针的引用。)但是,专门设计用于包含指针的链表可能会删除自身销毁的对象。

智能指针的链接列表可以在删除指针时自动删除对象,或者如果没有更多引用则会自动删除对象。这完全取决于你选择你想要的东西。

  

3)您是否想要手动调用析构函数?

不确定。一个例子是,如果你想用另一个相同类型的对象替换一个对象,但又不想释放内存只是为了再次分配它。您可以在适当的位置销毁旧对象并构建一个新对象。 (但是,通常这是一个坏主意。)

// pointer is destroyed because it goes out of scope,
// but not the object it pointed to. memory leak
if (1) {
 Foo *myfoo = new Foo("foo");
}


// pointer is destroyed because it goes out of scope,
// object it points to is deleted. no memory leak
if(1) {
 Foo *myfoo = new Foo("foo");
 delete myfoo;
}

// no memory leak, object goes out of scope
if(1) {
 Foo myfoo("foo");
}

答案 1 :(得分:13)

其他人已经解决了其他问题,所以我只想看一点:你是否想要手动删除一个对象。

答案是肯定的。 @DavidSchwartz给出了一个例子,但它是相当不寻常的一个。我将举例说明许多C ++程序员一直在使用的内容:std::vector(和std::deque,尽管它的使用率并不高。)

正如大多数人所知,{/ 1}}会在/当你添加的项目超过当前分配所能容纳的内容时分配更大的内存块。但是,当它执行此操作时,它有一块内存,能够容纳更多对象,而不是当前在向量中。

要管理它,std::vector所涵盖的内容是通过vector对象分配原始内存(除非另有说明,否则表示它使用{{1} }})。然后,当您使用(例如)Allocator将项添加到::operator new时,向量内部使用push_back在其内存中(先前)未使用的部分中创建项空间。

现在,如果您vector来自向量的项目,会发生什么?它不能只使用placement new - 它会释放整个内存块;它需要销毁该内存中的一个对象而不破坏任何其他对象,或者释放它控制的任何内存块(例如,如果你从向量中erase 5个项目,那么立即delete 5更多项目,保证,当你这样做时,矢量将重新分配内存。

为此,向量通过使用erase显式调用析构函数 not 直接销毁内存中的对象。

如果,另外,其他人使用连续存储来编写容器,就像push_back那样(或者delete确实存在的某些变体),你几乎肯定想要使用相同的技术。

例如,让我们考虑如何为循环环缓冲区编写代码。

vector

与标准容器不同,它直接使用std::deque#ifndef CBUFFER_H_INC #define CBUFFER_H_INC template <class T> class circular_buffer { T *data; unsigned read_pos; unsigned write_pos; unsigned in_use; const unsigned capacity; public: circular_buffer(unsigned size) : data((T *)operator new(size * sizeof(T))), read_pos(0), write_pos(0), in_use(0), capacity(size) {} void push(T const &t) { // ensure there's room in buffer: if (in_use == capacity) pop(); // construct copy of object in-place into buffer new(&data[write_pos++]) T(t); // keep pointer in bounds. write_pos %= capacity; ++in_use; } // return oldest object in queue: T front() { return data[read_pos]; } // remove oldest object from queue: void pop() { // destroy the object: data[read_pos++].~T(); // keep pointer in bounds. read_pos %= capacity; --in_use; } // release the buffer: ~circular_buffer() { operator delete(data); } }; #endif 。对于实际使用,你可能确实想要使用一个allocator类,但是目前它会分散注意力而不是贡献(IMO,无论如何)。

答案 2 :(得分:5)

  1. 使用new创建对象时,您有责任调用delete。当您使用make_shared创建对象时,生成的shared_ptr负责在使用次数变为零时保持计数并调用delete
  2. 超出范围确实意味着留下一个区块。这是在调用析构函数时,假设对象分配了new(即它是一个堆栈对象)。
  3. 关于唯一需要显式调用析构函数的时间是使用placement new分配对象时。

答案 3 :(得分:4)

1)对象不是“通过指针”创建的。有一个指针分配给你'新'的任何对象。假设这就是你的意思,如果你在指针上调用'delete',它实际上会删除(并调用析构函数)指针取消引用的对象。如果将指针指定给另一个对象,则会发生内存泄漏; C ++中没有任何内容可以为你收集垃圾。

2)这是两个不同的问题。当声明的堆栈帧从堆栈中弹出时,变量超出范围。通常这是你离开一个街区。堆中的对象永远不会超出范围,尽管它们在堆栈上的指针可能会。没有什么特别保证会调用链表中对象的析构函数。

3)不是真的。可能会有暗魔法暗示其他情况,但通常您希望将“新”关键字与“删除”关键字相匹配,并将所有必需品放入您的析构函数中,以确保它能够正确清理自己。如果您不这样做,请务必向使用该类的任何人详细说明析构函数,以了解他们应如何手动清理该对象的资源。

答案 4 :(得分:3)

要对问题3给出详细的答案:是的,有(很少)可以明确地调用析构函数,特别是作为一个新的展示位置的对应部分,正如dasblinkenlight观察到的那样。

举一个具体的例子:

#include <iostream>
#include <new>

struct Foo
{
    Foo(int i_) : i(i_) {}
    int i;
};

int main()
{
    // Allocate a chunk of memory large enough to hold 5 Foo objects.
    int n = 5;
    char *chunk = static_cast<char*>(::operator new(sizeof(Foo) * n));

    // Use placement new to construct Foo instances at the right places in the chunk.
    for(int i=0; i<n; ++i)
    {
        new (chunk + i*sizeof(Foo)) Foo(i);
    }

    // Output the contents of each Foo instance and use an explicit destructor call to destroy it.
    for(int i=0; i<n; ++i)
    {
        Foo *foo = reinterpret_cast<Foo*>(chunk + i*sizeof(Foo));
        std::cout << foo->i << '\n';
        foo->~Foo();
    }

    // Deallocate the original chunk of memory.
    ::operator delete(chunk);

    return 0;
}

这种事情的目的是将内存分配与对象构造分离。

答案 5 :(得分:2)

  1. 指针 - 常规指针不支持RAII。没有明确的delete,就会有垃圾。幸运的是,C ++有auto pointers可以为你处理这个问题!

  2. 范围 - 考虑变量何时变为不可见到您的程序。通常情况下,这是{block}的结尾,正如您所指出的那样。

  3. 手动销毁 - 切勿尝试此操作。让范围和RAII为你带来魔力。

答案 6 :(得分:1)

每当你使用“new”,即将地址附加到指针,或者说你在堆上声明空间时,你需要“删除”它。
1.yes,当你删除某些东西时,会调用析构函数 2.当链接列表的析构函数被调用时,它的对象的析构函数被调用。但如果它们是指针,则需要手动删除它们。 3.当空间被“新”声称时。

答案 7 :(得分:0)

是的,当一个对象超出作用域(如果它在堆栈上)或者在指向对象的指针上调用delete时,就会调用析构函数(a.k.a. dtor)。

  1. 如果通过delete删除指针,则会调用dtor。如果您在不先调用delete的情况下重新分配指针,则会出现内存泄漏,因为该对象仍然存在于内存中。在后一种情况下,不会调用dtor。

  2. 一个好的链表实现将在列表被销毁时调用列表中所有对象的dtor(因为你要么调用一些方法来破坏它,要么它超出了范围本身)。这取决于实现。

  3. 我对此表示怀疑,但如果有一些奇怪的情况,我不会感到惊讶。

答案 8 :(得分:0)

如果对象不是通过指针创建的(例如,A a1 = A();),则在对象被破坏时调用析构函数,总是在对象所在的函数完成时。例如:< BR />

void func()
{
...
A a1 = A();
...
}//finish

当代码被执行到“完成”行时,将调用析构函数
如果通过指针创建对象(例如,A * a2 = new A();),则在删除指针时调用析构函数(删除a2;)。如果用户未删除该点在删除之前明确地或给出新地址,发生内存泄漏。这是一个错误
在链表中,如果我们使用std :: list&lt;&gt;,我们不需要关心析构函数或内存泄漏,因为std :: list&lt;&gt;为我们完成了所有这些。在我们自己编写的链表中,我们应该编写析构函数并明确删除指针。否则会导致内存泄漏。
我们很少手动调用析构函数。它是为系统提供的功能 抱歉我的英语不好!

答案 9 :(得分:0)

请记住,在为对象分配内存之后立即调用对象的构造函数,而在释放该对象的内存之前立即调用析构函数。