基本问题:程序何时在C ++中调用类的析构函数方法?我被告知只要一个物体超出范围或受到delete
更具体的问题:
1)如果对象是通过指针创建的,并且该指针稍后被删除或者指定了一个新地址,那么它所指向的对象是否会调用它的析构函数(假设没有其他任何东西指向它)? / p>
2)继续问题1,什么定义了一个对象何时超出范围(不是关于对象何时离开给定的{block})。那么,换句话说,什么时候是一个在链表中调用对象的析构函数?
3)您是否想要手动调用析构函数?
答案 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)
new
创建对象时,您有责任调用delete
。当您使用make_shared
创建对象时,生成的shared_ptr
负责在使用次数变为零时保持计数并调用delete
。new
(即它是一个堆栈对象)。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)
指针 - 常规指针不支持RAII。没有明确的delete
,就会有垃圾。幸运的是,C ++有auto pointers可以为你处理这个问题!
范围 - 考虑变量何时变为不可见到您的程序。通常情况下,这是{block}
的结尾,正如您所指出的那样。
手动销毁 - 切勿尝试此操作。让范围和RAII为你带来魔力。
答案 6 :(得分:1)
每当你使用“new”,即将地址附加到指针,或者说你在堆上声明空间时,你需要“删除”它。
1.yes,当你删除某些东西时,会调用析构函数
2.当链接列表的析构函数被调用时,它的对象的析构函数被调用。但如果它们是指针,则需要手动删除它们。
3.当空间被“新”声称时。
答案 7 :(得分:0)
是的,当一个对象超出作用域(如果它在堆栈上)或者在指向对象的指针上调用delete
时,就会调用析构函数(a.k.a. dtor)。
如果通过delete
删除指针,则会调用dtor。如果您在不先调用delete
的情况下重新分配指针,则会出现内存泄漏,因为该对象仍然存在于内存中。在后一种情况下,不会调用dtor。
一个好的链表实现将在列表被销毁时调用列表中所有对象的dtor(因为你要么调用一些方法来破坏它,要么它超出了范围本身)。这取决于实现。
我对此表示怀疑,但如果有一些奇怪的情况,我不会感到惊讶。
答案 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)
请记住,在为对象分配内存之后立即调用对象的构造函数,而在释放该对象的内存之前立即调用析构函数。