在没有先显式调用析构函数的情况下在旧对象上使用new new是危险的吗?

时间:2013-12-12 11:15:32

标签: c++ placement-new

我想为对象回收内存而不是解除分配和重构它。假设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);
}

上面的简单示例编译并运行时没有错误,但我无法通过运行它来判断它是否会因为某种原因在更复杂的环境中爆炸。

3 个答案:

答案 0 :(得分:5)

这是安全的,因为你要覆盖的对象有一个简单的析构函数。从n3337开始,第3.8章(对象生存期):

  

4 程序可以通过重用对象占用的存储或显式地结束任何对象的生命周期   使用非平凡的析构函数调用类类型的对象的析构函数。对于类类型的对象   使用非平凡的析构函数,程序不需要在存储之前显式调用析构函数   对象占用的对象被重用或释放;但是,如果没有显式调用析构函数或者如果a   delete-expression(5.3.5)不用于释放存储,析构函数不应该被隐式调用   任何依赖于析构函数产生的副作用的程序都有未定义的行为。

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

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

答案 1 :(得分:5)

不,重复使用对象的内存并不危险,前提是您正确地执行了操作。此外,您不必将自己局限于没有指针的对象:通过显式调用析构函数,您可以准备对象以供重用,如下所示:

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

答案 2 :(得分:0)

已经有两个答案,但我担心这些答案不完整。

您可以重复使用对象的存储,只要您遵守以下几个条件:

  • 您无需使用动态分配的对象,任何对象都可以
  • 你应该通过调用它的析构函数(显式)来正确销毁前一个对象;如果析构函数具有副作用(参见§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...
}