26

我一直在阅读,当您使用placement new时,您必须手动调用析构函数。

考虑以下代码:

   // 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?

nullptr在第一种情况下,我们在调用析构函数后被迫将 pMyClass 设置为:

pMyClass->~MyClass();
pMyClass = nullptr;  // is that correct?

但是析构函数没有释放内存,对吧? 那么这会是内存泄漏吗?

我很困惑,你能解释一下吗?

4

4 回答 4

41

使用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,因此您还需要手动调用析构函数。由于您手动分配了内存,因此您需要手动释放它。

但是,placement new 也设计用于内部缓冲区(以及其他情况),其中缓冲区分配operator new,这就是为什么您不应该调用operator delete它们的原因。

#include <type_traits>

struct buffer_struct {
    std::aligned_storage_t<sizeof(MyClass), alignof(MyClass)> 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,注意两者是如何(几乎*)完全分开的。

*我们必须确保存储空间足够大

于 2012-01-18T23:15:51.630 回答
9

这是错误的一个原因:

delete pMyClass;

是你必须删除pMemorydelete[]因为它是一个数组:

delete[] pMemory;

你不能同时做上述两个。

类似的,你可能会问为什么不能用它malloc()来分配内存,placement new 来构造一个对象,然后delete去删除和释放内存。原因是您必须匹配malloc()and free(),而不是malloc()and delete

在现实世界中,几乎从不使用放置新的和显式的析构函数调用。它们可能由标准库实现在内部使用(或用于注释中指出的其他系统级编程),但普通程序员不会使用它们。在做 C++ 的很多年里,我从来没有在生产代码中使用过这样的技巧。

于 2012-01-18T23:04:03.677 回答
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分配和取消分配缓冲区 - 就放置新而言,缓冲区如何产生/被破坏并不重要。要点是将内存分配和对象生命周期的关注点分开。

顺便说一句,这可能的应用是在游戏中,您可能希望预先分配一大块内存,以便仔细管理您的内存使用情况。然后,您将在已获得的内存中构造对象。

另一种可能的用途是优化的小型、固定大小的对象分配器。

于 2012-01-18T23:38:27.933 回答
3

如果您想象在一块内存中构造多个MyClass 对象,这可能更容易理解。

在这种情况下,它会是这样的:

  1. 使用 new char[10*sizeof(MyClass)] 或 malloc(10*sizeof(MyClass)) 分配一个巨大的内存块
  2. 使用placement new 在该内存中构造十个MyClass 对象。
  3. 做点什么。
  4. 调用每个对象的析构函数
  5. 使用 delete[] 或 free() 释放大块内存。

如果您正在编写编译器或操作系统等,您可能会这样做。

在这种情况下,我希望清楚为什么需要单独的“析构函数”和“删除”步骤,因为没有理由调用 delete。但是,您应该释放内存,但通常会这样做(释放、删除、对巨大的静态数组不执行任何操作,如果数组是另一个对象的一部分等则正常退出等),如果您不这样做'会被泄露。

另请注意,正如 Greg 所说,在这种情况下,您不能使用 delete,因为您使用 new[] 分配了数组,因此您需要使用 delete[]。

另请注意,您需要假设您没有为 MyClass 覆盖删除,否则它将做一些完全不同的事情,这几乎肯定与“新”不兼容。

所以我认为你不太可能像你描述的那样称呼“删除”,但它可以工作吗?我认为这与“我有两个没有析构函数的不相关类型。我可以新建一个指向一种类型的指针,然后通过指向另一种类型的指针删除该内存吗?”基本上是同一个问题?换句话说,“我的编译器是否有一个所有分配的东西的大列表,或者它可以为不同的类型做不同的事情”。

恐怕我不确定。阅读它说的规范:

5.3.5 ...如果[删除运算符]操作数的静态类型与其动态类型不同,则静态类型应为操作数动态类型的基类,静态类型应具有虚拟析构函数或行为未定义。

我认为这意味着“如果你使用两种不相关的类型,它就不起作用(可以通过虚拟析构函数多态地删除类对象)。”

所以不,不要那样做。我怀疑它在实践中可能经常起作用,如果编译器确实只查看地址而不是类型(并且这两种类型都不是多重继承类,这会破坏地址),但不要尝试它。

于 2012-01-19T00:06:03.703 回答