基本问题:程序何时在C ++中调用类的析构函数方法?有人告诉我,只要对象超出范围或受到delete
更具体的问题:
1)如果对象是通过指针创建的,并且该指针后来被删除或提供了指向的新地址,那么指向该对象的对象是否调用其析构函数(假设没有其他对象指向该析构函数)?
2)跟进问题1,由什么定义对象何时超出范围(与对象何时离开给定的{block}无关)。因此,换句话说,何时在链接列表中的对象上调用析构函数?
3)您是否想手动调用析构函数?
基本问题:程序何时在C ++中调用类的析构函数方法?有人告诉我,只要对象超出范围或受到delete
更具体的问题:
1)如果对象是通过指针创建的,并且该指针后来被删除或提供了指向的新地址,那么指向该对象的对象是否调用其析构函数(假设没有其他对象指向该析构函数)?
2)跟进问题1,由什么定义对象何时超出范围(与对象何时离开给定的{block}无关)。因此,换句话说,何时在链接列表中的对象上调用析构函数?
3)您是否想手动调用析构函数?
Answers:
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");
}
new Foo()
是大写的“ F”。)
Foo myfoo("foo")
不是多数烦恼解析,而是char * foo = "foo"; Foo myfoo(foo);
。
delete myFoo
之前不应该这样Foo *myFoo = new Foo("foo");
吗?否则,您将删除新创建的对象,不是吗?
myFoo
前Foo *myFoo = new Foo("foo");
行。该行创建了一个全新的变量,称为myFoo
,覆盖了现有变量。尽管在这种情况下,由于myFoo
上述内容已在的范围内,因此已不存在if
。
其他人已经解决了其他问题,所以我只看一点:您是否要手动删除对象。
答案是肯定的。@DavidSchwartz举了一个例子,但这是一个非常不寻常的例子。我将举一个例子说明很多C ++程序员一直使用的东西:(std::vector
而且std::deque
,尽管使用的不是很多)。
如大多数人所知,std::vector
当/如果添加的项目超出其当前分配的容量,将分配更大的内存块。但是,执行此操作时,它具有一块内存,可以容纳比向量中当前更多的对象。
为了解决这个问题,vector
幕后工作是通过对象分配原始内存Allocator
(除非另有说明,否则它表示使用::operator new
)。然后,当您使用(例如)push_back
将项添加到时vector
,向量在内部使用a placement new
在其存储空间的(先前)未使用的部分中创建一个项。
现在,如果/如果您erase
从向量中得到一个项目,会发生什么?它不能只使用delete
-会释放其整个内存块;它需要销毁该内存中的一个对象,而又不销毁其他任何对象,也不释放它控制的任何内存块(例如,如果您erase
从一个向量中减去push_back
5个项目,然后立即再增加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
的对象时。1)对象不是通过“指针”创建的。有一个指针分配给您“新建”的任何对象。假设这是您的意思,如果您在指针上调用“删除”,它将实际上删除(并在其上调用析构函数)指针取消引用的对象。如果将指针分配给另一个对象,则会发生内存泄漏;C ++中没有任何东西可以为您收集垃圾。
2)这是两个独立的问题。当声明其所在的堆栈帧从堆栈弹出时,变量超出范围。通常是在您离开街区时。堆中的对象永远不会超出范围,尽管它们在堆栈上的指针可能会超出范围。尤其不能保证将调用链表中对象的析构函数。
3)不是。可能会有Deep Magic提出其他建议,但是通常您希望将“ new”关键字与“ delete”关键字匹配,并将所有必要的东西放入析构函数中,以确保其正确清理。如果您不这样做,请确保对析构函数进行注释,并向使用该类的任何人提供特定的说明,说明他们应如何手动清理该对象的资源。
为了对问题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;
}
这种事情的目的是使内存分配与对象构造脱钩。
指针 -常规指针不支持RAII。没有显式的delete
,就会有垃圾。幸运的是,C ++具有自动指针可以为您处理此问题!
范围 -考虑变量何时对程序不可见。{block}
如您所指出的,通常在的末尾。
手动销毁 -切勿尝试。只要让scope和RAII为您做魔术即可。
std::auto_ptr
在C ++ 11中已弃用,是的。如果OP实际具有C ++ 11,则他std::unique_ptr
应用于单个所有者,或std::shared_ptr
用于引用计数的多个所有者。
std::queue<std::shared_ptr>?
我发现pipe()
,如果复制不是太昂贵,则在生产者线程和使用者线程之间会使并发变得容易得多。
是的,当对象超出范围(如果它在堆栈中)或调用delete
对象的指针时,将调用析构函数(也称为dtor)。
如果通过删除了指针delete
,则将调用dtor。如果在没有delete
先调用的情况下重新分配了指针,则会因为对象仍然存在于内存中而导致内存泄漏。在后一种情况下,不调用dtor。
一个好的链表实现会在销毁列表时调用列表中所有对象的dtor(因为您调用了某种方法来销毁它或它超出了范围)。这取决于实现。
我对此表示怀疑,但是如果有一些奇怪的情况,我不会感到惊讶。
如果对象不是通过指针创建的(例如A a1 = A();),则在销毁对象时调用析构函数,始终在对象所在的函数完成时调用,例如:
void func()
{
...
A a1 = A();
...
}//finish
当代码执行到“ finish”行时,将调用析构函数。
如果对象是通过指针创建的(例如A * a2 = new A();),则在删除指针(删除a2;)时调用析构函数。新地址删除之前,发生内存泄漏。那是一个错误。
在链接列表中,如果我们使用std :: list <>,则不必关心解码器或内存泄漏,因为std :: list <>已为我们完成了所有这些操作。在我们自己编写的链表中,我们应该编写描述符并明确删除指针,否则会导致内存泄漏。
我们很少手动调用析构函数。它是为系统提供的功能。
对不起,我的英语不好!