何时调用C ++析构函数?
基本问题:程序何时在C ++中调用类的析构函数方法? 我被告知,只要对象超出范围或遭到delete
,就会调用它
更具体的问题:
1)如果该对象是通过指针创建的,并且该指针稍后被删除或指定了新的地址,那么它所指向的对象是否调用其析构函数(假设没有别的指向它)?
2)在问题1之后,什么定义了什么时候一个对象超出了范围(而不是关于什么时候一个对象离开给定的{block})。 换句话说,何时在链表中调用析构函数?
3)你是否想要手动调用析构函数?
1)如果该对象是通过指针创建的,并且该指针稍后被删除或指定了新的地址,那么它所指向的对象是否调用其析构函数(假设没有别的指向它)?
这取决于指针的类型。 例如,智能指针在被删除时经常删除它们的对象。 普通的指针不会。 指针指向不同的对象时也是如此。 一些智能指针会破坏旧的对象,或者如果它没有更多的引用将会销毁它。 普通的指针没有这样的智慧。 他们只是持有一个地址,并允许您通过特定的操作对他们指向的对象执行操作。
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");
}
其他人已经解决了其他问题,所以我只看一点:你是否想要手动删除一个对象。
答案是肯定的。 @DavidSchwartz举了一个例子,但这是一个相当不寻常的例子。 我将给出一个很多C ++程序员一直都在使用的例子: std::vector
(和std::deque
,虽然它的用处不大)。
正如大多数人所知, std::vector
会在/添加更多的项目而不是当前的分配可以容纳的时候分配更大的内存块。 然而,当它这样做时,它有一块内存可以容纳比当前在矢量中更多的对象。
为了解决这个问题,下面介绍的vector
是通过Allocator
对象分配原始内存(除非另有说明,否则表示它使用::operator new
)。 然后,当您使用(例如) push_back
向该vector
添加一个项目时,该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;
}
// release the buffer:
~circular_buffer() { operator delete(data); }
};
#endif
与标准容器不同,它直接使用operator new
和operator delete
。 对于真正的使用,你可能确实想使用分配器类,但目前它会做更多的分心而不是贡献(IMO,无论如何)。
new
创建一个对象时,你负责调用delete
。 当您使用make_shared
创建对象时,生成的shared_ptr
负责在使用计数变为零时保持计数并调用delete
。 new
(即它是一个堆栈对象)。 new
对象分配对象时。