基本问题:在C++中,程序何时调用类的析构函数?有人告诉我,每当一个对象超出其作用域或被delete掉时都会被调用。
更具体的问题:
1)如果对象是通过指针创建的,并且该指针稍后被删除或给予一个新地址来指向,假设没有其他指针指向这个对象,那么它所指向的对象会调用它的析构函数吗?
2)关于问题1的跟进,什么定义了一个对象何时超出其作用域(不涉及何时离开给定{块})。换句话说,在链表中什么时候调用对象的析构函数?
3)你是否需要手动调用析构函数?
基本问题:在C++中,程序何时调用类的析构函数?有人告诉我,每当一个对象超出其作用域或被delete掉时都会被调用。
更具体的问题:
1)如果对象是通过指针创建的,并且该指针稍后被删除或给予一个新地址来指向,假设没有其他指针指向这个对象,那么它所指向的对象会调用它的析构函数吗?
2)关于问题1的跟进,什么定义了一个对象何时超出其作用域(不涉及何时离开给定{块})。换句话说,在链表中什么时候调用对象的析构函数?
3)你是否需要手动调用析构函数?
1) 如果对象是通过指针创建的,而该指针后来被删除或指向了新的地址,那么原本被指向的对象是否调用其析构函数(假设没有其他指针指向它)?
这取决于指针类型。例如,智能指针在被删除时通常会删除它们所指向的对象。普通指针则不会。当指针指向不同的对象时情况也一样。一些智能指针会销毁旧对象或在旧对象不再有引用时将其销毁。普通指针没有这种智能功能。它们只是保存一个地址,并允许您通过特定操作对其所指向的对象执行操作。
2) 接着问题1,什么定义了对象何时超出作用域(与对象离开给定{块}无关)。换句话说,在链表中什么时候会调用对象的析构函数?
这取决于链表的实现。典型的集合在销毁时会销毁其中包含的所有对象。
因此,指向指针的链表通常会销毁指针但不会销毁它们所指向的对象。(这可能是正确的。它们可能被其他指针引用。)而专门设计用于包含指针的链表可能会在其自身销毁时删除对象。
智能指针的链表可以在删除指针时自动删除对象,或者在它们没有更多引用时进行删除。您可以选择符合您要求的代码。
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");
}
new Foo()
。) - Stuart GolodetzFoo myfoo("foo")
不是最令人烦恼的解析问题,但是 char * foo = "foo"; Foo myfoo(foo);
是。 - Cosinedelete myFoo
不应该在 Foo *myFoo = new Foo("foo");
之前调用吗?否则你会删除刚刚创建的对象,不是吗? - Matheus Rochastd::vector
(和std::deque
,尽管它没有被广泛使用)。std::vector
将分配一个更大的内存块。然而,当它这样做时,它有一个能够容纳比向量中当前物体更多物体的内存块。vector
在底层使用Allocator
对象(除非你另外指定,否则它使用::operator new
)分配原始内存。然后,当你使用(例如)push_back
将项添加到vector
时,在其内部使用placement new
在其内存空间的(以前)未使用部分创建一个项。erase
一个项目会发生什么?它不能只使用delete
——那会释放它的整个内存块;它需要在该内存中销毁一个对象,而不销毁任何其他对象或释放它控制的任何内存块(例如,如果你从向量中erase
5个项目,然后立即push_back
5个项目,那么当你这样做时,向量保证不会重新分配内存)。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;
}
~circular_buffer() {
// first destroy any content
while (in_use != 0)
pop();
// then release the buffer.
operator delete(data);
}
};
#endif
operator new
和 operator delete
直接分配和释放内存。实际使用中,您可能确实需要使用一个分配器类,但目前来说,这只会更加分散注意力而非贡献(在我看来)。delete
将删除为向量分配的整个数组?这怎么可能,我们只删除向量中的一个元素。 - starrietnew
创建对象时,您需要负责调用delete
。如果使用make_shared
创建对象,则生成的shared_ptr
将负责计数并在使用计数归零时调用delete
。new
分配(即它是一个栈对象)。new
来分配该对象时。1) 对象不是通过指针创建的。对于任何你使用 'new' 创建的对象,都会有一个指向它的指针被赋值。假设这就是你的意思,如果你在指针上调用 'delete',它将删除(并调用指针引用的对象的析构函数)。如果你将指针赋给另一个对象,就会产生内存泄漏;在 C++ 中没有任何东西会为你收集垃圾。
2) 这是两个不同的问题。当声明变量的堆栈帧从堆栈中弹出时,变量就会超出其范围。通常情况下,这是在离开代码块时发生的。堆中的对象永远不会超出其范围,尽管它们在堆栈上的指针可能会超出其范围。没有什么特别的保证一个链接列表中的对象的析构函数会被调用。
3) 不完全是。也许有一些特殊的魔法可以表明相反,但通常你想让你的 'new' 关键字与你的 'delete' 关键字匹配,并在你的析构函数中放入所有必要的内容以确保它能够适当地清理自己。如果你不这样做,请确保用具体的说明在注释中告诉任何使用该类的人如何手动清除该对象的资源。
#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;
}
指针 -- 普通指针不支持RAII。没有显式的delete
,会导致内存泄漏。幸运的是,C++有auto pointers可以为您处理这些问题!
作用域 -- 当变量对程序不可见时,就可以考虑其作用域。通常情况下,在{block}
的结尾处即可。
手动销毁 -- 永远不要尝试手动销毁。只需让作用域和RAII为您完成魔法。
std::auto_ptr
在C++11中已经被弃用。如果OP确实使用了C++11,他应该使用std::unique_ptr
来处理单独所有权的情况,或者使用std::shared_ptr
来处理引用计数的多个所有权的情况。 - chrisaycockstd::queue<std::shared_ptr>
吗?我发现在生产者和消费者线程之间使用pipe()
可以使并发变得更容易,如果复制不太昂贵的话。 - chrisaycock记住,一个对象的构造函数会在为该对象分配内存后立即调用,而析构函数则会在释放该对象的内存之前调用。
void func()
{
...
A a1 = A();
...
}//finish
当代码执行到"finish"行时,析构函数会被调用。
如果对象是通过指针创建的(例如:A * a2 = new A();),则在删除指针(delete a2;)时会调用析构函数。如果用户没有显式地删除指针或在删除之前给它一个新地址,则会发生内存泄漏。这是一个错误。
在链表中,如果我们使用std::list<>,我们不需要关心析构函数或内存泄漏,因为std::list<>已经为我们完成了所有这些工作。在自己编写的链表中,我们应该编写析构函数并显式地删除指针。否则,将导致内存泄漏。
我们很少手动调用析构函数。它是系统提供的一个函数。
对于我的糟糕英语表示抱歉!
是的,析构函数(也称为 dtor)在对象超出范围时被调用,如果它在堆栈上或者当您对指向对象的指针调用 delete
时。
如果通过 delete
删除指针,则将调用 dtor。如果您在不首先调用 delete
的情况下重新分配指针,则会发生内存泄漏,因为对象仍然存在于某个地方的内存中。在后一种情况下,dtor 不会被调用。
一个好的链表实现将在列表被销毁时(因为您要么调用了某个方法来销毁它,要么它本身超出了范围),调用列表中所有对象的 dtor。这取决于具体实现。
我怀疑,但如果有一些奇怪的情况,我也不会感到惊讶。