我一直在阅读,当您使用展示位置时,您必须手动调用析构函数。
考虑下面的代码:
// Allocate memory ourself
char* pMemory = new char[ sizeof(MyClass)];
// Construct the object ourself
MyClass* pMyClass = new( pMemory ) MyClass();
// The destruction of object is our duty.
pMyClass->~MyClass();
据我所知,运算符delete
通常会调用析构函数然后释放内存,对吧?那么为什么我们不使用delete
呢?
delete pMyClass; //what's wrong with that?
在第一种情况下,在我们调用这样的析构函数后,我们被迫将pMyClass设置为nullptr
:
pMyClass->~MyClass();
pMyClass = nullptr; // is that correct?
但是析构函数没有释放内存,对吗? 那会是内存泄漏吗?
我很困惑,你能解释一下吗?
答案 0 :(得分:36)
使用new
运算符执行两项操作,它调用分配内存的函数operator new
,然后使用placement new来在该内存中创建对象。 delete
运算符调用对象的析构函数,然后调用operator delete
。是的,这些名字令人困惑。
//normal version calls these two functions
MyClass* pMemory = new MyClass; void* pMemory = operator new(sizeof(MyClass));
MyClass* pMyClass = new( pMemory ) MyClass();
//normal version calls these two functions
delete pMemory; pMyClass->~MyClass();
operator delete(pMemory);
因为在您的情况下,您手动使用了placement new,所以还需要手动调用析构函数。由于您手动分配了内存,因此需要手动释放它。请注意,如果您使用new
进行分配,则必须有一个delete
。总是
但是,placement new也适用于内部缓冲区(和其他场景),其中缓冲区不分配operator new
,这就是你不应该调用的原因operator delete
就可以了。
#include <type_traits>
struct buffer_struct {
std::aligned_storage<sizeof(MyClass)>::type buffer;
};
int main() {
buffer_struct a;
MyClass* pMyClass = new (&a.buffer) MyClass(); //created inside buffer_struct a
//stuff
pMyClass->~MyClass(); //can't use delete, because there's no `new`.
return 0;
}
buffer_struct
类的目的是以任何方式创建和销毁存储,而main
负责MyClass
的构建/销毁,注意两者是如何(几乎*)彼此完全分开。
*我们必须确保存储空间足够大
答案 1 :(得分:9)
这是错误的一个原因:
delete pMyClass;
您必须使用pMemory
删除delete[]
,因为它是一个数组:
delete[] pMemory;
您不能同时执行上述两项操作。
同样,您可能会问为什么不能使用malloc()
来分配内存,使用new来构造对象,然后delete
来删除和释放内存。原因是您必须匹配malloc()
和free()
,而不是malloc()
和delete
。
在现实世界中,几乎从不使用放置新的和显式的析构函数调用。它们可能在标准库实现中内部使用(或者在注释中指出的其他系统级编程),但普通程序员不会使用它们。在使用C ++的许多年里,我从未在生产代码中使用过这些技巧。
答案 2 :(得分:4)
您需要区分delete
运算符和operator delete
。特别是,如果您使用的是placement new,则显式调用析构函数然后调用operator delete
(而不是delete
运算符)来释放内存,即
X *x = static_cast<X*>(::operator new(sizeof(X)));
new(x) X;
x->~X();
::operator delete(x);
请注意,这使用operator delete
,它比delete
运算符更低级,并且不担心析构函数(它实际上有点像free
)。将其与delete
运算符进行比较,该运算符在内部相当于调用析构函数并调用operator delete
。
值得注意的是,您不必使用::operator new
和::operator delete
来分配和释放缓冲区 - 就放置新位置而言,缓冲区如何进入并不重要被摧毁。重点是分离内存分配和对象生存期的问题。
顺便提一下,这可能是一种类似游戏的应用,你可能需要预先分配一大块内存才能仔细管理内存使用情况。然后,您将在已经获得的内存中构造对象。
另一种可能的用途是使用优化的小型固定大小的对象分配器。
答案 3 :(得分:3)
如果你想象在一个内存块中构建几个 MyClass对象,可能会更容易理解。
在这种情况下,它会像:
如果您正在编写编译器或操作系统等,那么您可能会这样做。
在这种情况下,我希望很明显为什么你需要单独的“析构函数”和“删除”步骤,因为没有理由你将调用delete。但是,应释放内存,但通常会这样做(免费,删除,对巨型静态数组不执行任何操作,如果数组是另一个对象的一部分则正常退出等),以及如果你不这样做会被泄露。
另请注意,正如Greg所说,在这种情况下,你不能使用delete,因为你用new []分配了数组,所以你需要使用delete []。
另请注意,您需要假设您没有覆盖MyClass的删除,否则它会做一些完全不同的事情,这几乎肯定与“新”不兼容。
所以我认为你不想像你所描述的那样打电话给“删除”,但它能不能用?我认为这基本上是同一个问题,“我有两个没有析构函数的无关类型。我可以新建一个指向一个类型的指针,然后通过指向另一个类型的指针删除那个内存吗?”换句话说,“我的编译器是否有一个包含所有已分配内容的大列表,或者它可以针对不同类型执行不同的操作”。
我担心我不确定。阅读规范说:
5.3.5 ...如果[删除运算符]的操作数的静态类型与其动态类型不同,则静态类型应为基数 操作数的类动态类型和静态类型应具有 虚析构函数或行为未定义。
我认为这意味着“如果您使用两个不相关的类型,它就不起作用(可以通过虚拟析构函数以多态方式删除类对象)。”
所以不,不要那样做。我怀疑它可能经常在实践中工作,如果编译器确实只查看地址而不是类型(并且这两个类型都不是多继承类,这会破坏地址),但不要尝试它。