在不先显式调用析构函数的情况下,在旧对象上使用placement new是否危险

Is it dangerous to use placement new on an old object without explicitly calling the destructor first?

本文关键字:对象 placement 危险 是否 new 调用 析构函数 情况下      更新时间:2023-10-16

我想为对象回收内存,而不是释放和重建它。假设Foo在实践中不包含指针(但可能包含函数),下面使用"placement new"是否安全?

此外,最后的delete调用是否安全,它是否会正确地调用第二个"新"对象上的析构函数,并在之后正确地释放内存?

#include <new>
struct Foo {
    int hello;
    int world;
};
int main() {
    Foo* foo = new Foo;
    // Do something with foo
    // Done with foo, writing a new version of foo on top of the old one.
    new(foo) Foo();
    delete(foo);
}

上面这个简单的例子编译和运行都没有错误,但我无法通过运行它来判断它是否会在更复杂的环境中因某种原因而崩溃。

它是安全的,因为您覆盖的对象有一个微不足道的析构函数。来自n3337,第3.8章(对象生存期):

4程序可以通过重用对象所占用的存储或显式地终止任何对象的生命周期使用非平凡的析构函数为类类型的对象调用析构函数对于类类型的对象对于非平凡的析构函数,程序不需要在存储之前显式调用析构函数物体所占据的空间被重新使用或释放;但是,如果没有显式调用析构函数,或者如果delete表达式(5.3.5)不用于释放存储,则不应隐式调用析构函数,并且任何依赖于析构函数产生的副作用的程序都有未定义的行为。

delete呼叫也是安全的。你在从new得到的指针上调用它,在那个地方有一个活动对象。

正如你在问题中暗示的那样,如果析构函数是非平凡的并且有副作用,它可能会调用未定义的行为——在这种情况下,你需要显式地调用它。类是否包含指针并不直接重要——即使在这种情况下,重用存储也是安全的,但当然,这样可能会引入内存泄漏和其他错误。

不,如果您做得正确,重用对象的内存并不危险。此外,您不必将自己限制在没有指针的对象上:通过显式调用析构函数,您可以准备重用对象,如下所示:

Foo* foo = new Foo;
// Do something with foo
// Done with foo, writing a new version of foo on top of the old one.
foo->~Foo();     // Call the destructor explicitly to clean up the resources of a Foo
new(foo) Foo();  // Place new data into the previously allocated memory
delete(foo);     // We are deleting a fully initialized object, so it is OK

已经有两个答案了,但我担心它们给出了一个不完整的画面。

您可以重用对象的存储,前提是您尊重以下几个条件:

  • 您不需要使用动态分配的对象,任何对象都可以
  • 您应该通过(显式)调用其析构函数来正确地销毁前一个对象;如果析构函数有副作用,则不这样做会导致未定义的行为(参见§3.8/4)
  • 放置的对象应具有与上一个对象相同的动态类型(请参见§3.8/7)

让我们回顾一下,从开始,任何对象都可以:

struct Foo {
    int hello;
    int world;
};
void automatically_allocated() {
    Foo foo;
    foo.~Foo();
    new (&foo) Foo{};
}
void dynamically_allocated() {
    std::unique_ptr<Foo> foo(new Foo{});
    foo->~Foo();
    new (&*foo) Foo{};
}

继续使用销毁前一个对象:

struct Bar {
    int hello;
    std::string world;
};
void UNDEFINED_BEHAVIOR() {
    Bar bar;
    new (&bar) Bar{}; // most likely scenario: leaks memory owned by bar.world
}

最后使用相同的动态类型

struct Base { virtual ~Base() {} };
struct Derived: Base { std::string world; };
struct Other: Base { int hello; }
void UNDEFINED_BEHAVIOR() {
    Derived derived;
    Base& b = derived;
    b.~Base(); // fine
    new (&b) Other{};
    // Most likely here, calls "derived.~Derived()" on an object of type Other...
}