什么时候调用C ++析构函数?

基本问题:程序什么时候在C ++中调用类的析构函数? 我被告知,只要对象超出范围或遭到delete ,就会调用它

更具体的问题:

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

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

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

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

这取决于指针的types。 例如,智能指针在被删除时往往会删除它们的对象。 普通的指针不会。 指针指向不同的对象时也是如此。 一些聪明的指针会破坏旧的对象,或者如果没有更多的引用将会销毁它。 普通的指针没有这样的智慧。 他们只是持有一个地址,并允许您通过特定的操作对他们指向的对象进行操作。

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

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

所以,链接的指针列表通常会销毁指针,但不会指向它们指向的对象。 (这可能是正确的,它们可能是其他指针的引用)。然而,专门devise用于包含指针的链表可能会删除自身销毁的对象。

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

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

当然。 一个例子是如果你想用另一个相同types的对象replace一个对象,但不想释放内存只是为了再次分配它。 你可以摧毁旧的物体,并build立一个新的。 (但是,通常这是一个坏主意。)

 // 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"); } 

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

答案是肯定的。 @DavidSchwartz举了一个例子,但这是一个相当不寻常的例子。 我将给出一个很多C ++程序员一直都在使用的例子: std::vector (和std::deque ,虽然没有那么多)。

大多数人都知道,当/添加比当前分配更多的项时, std::vector会分配一个更大的内存块。 但是,当它执行此操作时,它具有一个内存块,能够容纳比当前在vector中更多的对象。

为了pipe理这个问题,下面介绍的是通过Allocator对象来分配原始内存(除非另外指定,否则表示使用::operator new )。 然后,当您使用(例如) push_back向该vector添加一个项目时,该vector内部使用placement new来在其内存空间的(先前)未使用的部分中创build一个项目。

现在,如果从vector中erase一个项目,会发生什么情况? 它不能只使用delete – 这将释放其整个内存块; 它需要在该内存中销毁一个对象而不会破坏其他任何对象,或者释放它所控制的任何内存块(例如,如果erase一个向量中的5个项目,然后立即push_back 5个项目,则保证该向量将这样做时重新分配内存。

要做到这一点,vector通过显式调用析构函数直接破坏内存中的对象, 而不是使用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 

与标准容器不同,它直接使用operator newoperator delete 。 对于真正的使用,你可能确实想使用一个分配器类,但目前它会做更多的分心而不是贡献(IMO,无论如何)。

  1. 当你用new创build一个对象时,你负责调用delete 。 当你用make_shared创build一个对象时,结果shared_ptr负责保持计数和调用delete当使用计数为零。
  2. 走出范围确实意味着留下一个块。 这是当析构函数被调用时,假设对象没有被分配new (即它是一个堆栈对象)。
  3. 关于唯一需要显式调用析构函数的情况是当您为new位置分配对象时。

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

2)这是两个不同的问题。 当一个variables被声明的堆栈框被popup堆栈时,一个variables超出范围。 通常这是当你离开一个块。 堆中的对象不会超出范围,尽pipe堆栈中的指针可能会超出范围。 没有什么特别保证链表中的对象的析构函数将被调用。

3)不是真的。 有可能是深魔法,否则会build议,但通常你想匹配你的“新”关键字与你的“删除”关键字,并把所有必要的破坏者,以确保它正确清理自己。 如果你不这样做,一定要评论析构函数具体说明给任何使用类的人应该如何手动清理对象的资源。

为了给问题3提供详细的答案:是的,在很less的情况下,你可以明确地调用析构函数,特别是作为新的放置对象,就像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; } 

这种事情的目的是从内存构造中分离内存分配。

  1. 指针 – 常规指针不支持RAII。 没有明确的delete ,就会有垃圾。 幸运的是C ++有自动指针来处理这个问题!

  2. 范围 – 考虑一个variables何时变得对你的程序不可见 。 通常这是在{block}的末尾,正如你指出的那样。

  3. 手动销毁 – 切勿尝试此操作。 让范围和RAII为你做魔术。

每当你使用“new”,就是把一个地址附加到一个指针上,或者说,你声称在堆上有空间时,你需要“删除”它。
1.当你删除某个东西时,析构函数被调用。
2.当链表的析构函数被调用时,它的对象的析构函数被调用。 但是,如果他们是指针,你需要手动删除它们。 3.当空间被“新”声称。

是的,如果一个对象超出了作用域,或者在你指向一个对象的指针时调用了delete ,则会调用析构函数(aka dtor)。

  1. 如果通过delete指针被删除,那么dtor将被调用。 如果重新分配指针而不先调用delete ,则会发生内存泄漏,因为该对象仍然存在于内存中。 在后面的例子中,dtor不被调用。

  2. 一个好的链表实现将在列表被销毁时调用列表中所有对象的dtor(因为你调用了一些方法来剥夺它或者它本身超出了作用域)。 这是依赖于实现的。

  3. 我怀疑这一点,但如果在那里有一些奇怪的情况,我不会感到惊讶。

如果对象不是通过指针创build的(例如,A a1 = A();),则当对象所在的函数完成时,析构函数会在对象被破坏时调用,例如:

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

当代码执行到“完成”行时,析构函数被调用。

如果对象是通过指针创build的(例如,A * a2 = new A();),则在删除指针时删除该指针(删除a2;);如果该指针未被用户明确删除或给定新的地址在删除之前,发生内存泄漏。 这是一个错误。

在链表中,如果我们使用std :: list <>,我们不需要关心desctructor或内存泄漏,因为std :: list <>已经完成了所有这些。 在我们自己编写的链表中,我们应该写下desctructor并删除指针,否则会导致内存泄漏。

我们很less手动调用析构函数。 这是一个为系统提供的function。

对不起我的英文不好!