C++ 如何正确释放新位置分配的内存?

C++ 如何正确释放新位置分配的内存?,c++,placement-new,C++,Placement New,我一直在读一些文章,当你使用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. pMyCl

我一直在读一些文章,当你使用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?
在第一种情况下,在调用析构函数后,我们被迫将pMyClass设置为
nullptr
,如下所示:

pMyClass->~MyClass();
pMyClass = nullptr;  // is that correct?
但是析构函数没有释放内存,对吗? 那会是内存泄漏吗


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

这是错误的一个原因:

delete pMyClass;
您必须使用
delete[]
删除
pMemory
,因为它是一个数组:

delete[] pMemory;
你不能同时做以上两件事

类似地,您可能会问,为什么不能使用
malloc()
分配内存、放置新对象以构造对象,然后使用
delete
删除并释放内存。原因是您必须匹配
malloc()
free()
,而不是
malloc()
delete


在现实世界中,几乎从未使用过新的显式析构函数调用。它们可能由标准库实现内部使用(或用于注释中提到的其他系统级编程),但普通程序员不使用它们。多年来我一直没有使用过这样的技巧来制作C++代码。

< p>使用<代码> new < /Calp>表达式做两件事,它调用函数<代码>运算符new <代码>,分配内存,然后使用布局new,在内存中创建对象。
delete
表达式调用对象的析构函数,然后调用
操作符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;
}
#包括
结构缓冲区{
标准:对齐的存储缓冲区;
};
int main(){
缓冲区结构a;
MyClass*pMyClass=new(&a.buffer)MyClass();//在缓冲区结构a中创建
//东西
pMyClass->~MyClass();//无法使用delete,因为没有“new”。
返回0;
}
buffer\u struct
类的目的是以任何方式创建和销毁存储,而
main
负责构建/销毁
MyClass
,请注意这两者是如何(几乎*)完全分开的


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

您需要区分
delete
操作符和
操作符delete
。特别是,如果使用placement new,则显式调用析构函数,然后调用
运算符delete
(而不是
delete
运算符)释放内存,即

X *x = static_cast<X*>(::operator new(sizeof(X)));
new(x) X;
x->~X();
::operator delete(x);
X*X=static_cast(::操作符new(sizeof(X));
新(x)x;
x->~x();
::运算符删除(x);
请注意,这使用了
操作符delete
,它的级别低于
delete
操作符,并且不需要担心析构函数(它本质上有点像
free
)。将其与
delete
操作符进行比较,后者在内部相当于调用析构函数并调用
operator delete

值得注意的是,您不必使用
::operator new
::operator delete
来分配和解除分配您的缓冲区-就placement new而言,缓冲区是如何产生/被破坏的并不重要。主要的一点是将内存分配和对象生存期分离开来

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


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

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

在这种情况下,会出现如下情况:

  • 使用新字符[10*sizeof(MyClass)]或malloc(10*sizeof(MyClass))分配一个巨大的内存块
  • 使用placement new在该内存中构造十个MyClass对象
  • 做点什么
  • 调用每个对象的析构函数
  • 使用delete[]或free()释放大内存块
  • 如果您正在编写编译器或操作系统等,您可能会这样做

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

    另请注意,正如Greg所说,在本例中,您不能使用delete,因为您为数组分配了new[],因此需要使用delete[]

    还要注意的是,您需要假设您没有覆盖MyClass的delete,否则它将执行完全不同的操作,这几乎肯定与“new”不兼容

    所以我认为你不太可能像你所描述的那样称之为“删除”,但它能起作用吗?我认为这与“我有两个没有析构函数的不相关类型。我可以新建一个指向一个类型的指针,然后通过指向另一个类型的指针删除该内存吗?”基本相同,换句话说,“我的编译器有一个大的li吗?”