如何正确释放新安置分配的内存?

时间:2012-01-18 23:01:37

标签: c++ 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?

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

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

4 个答案:

答案 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对象,可能会更容易理解。

在这种情况下,它会像:

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

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

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

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

    所以我认为你不想像你所描述的那样打电话给“删除”,但它能不能用?我认为这基本上是同一个问题,“我有两个没有析构函数的无关类型。我可以新建一个指向一个类型的指针,然后通过指向另一个类型的指针删除那个内存吗?”换句话说,“我的编译器是否有一个包含所有已分配内容的大列表,或者它可以针对不同类型执行不同的操作”。

    我担心我不确定。阅读规范说:

      

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

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

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

相关问题