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

908 views Asked by At

I would like to recycle memory for an object rather than deallocating and reconstructing it. Is the following usage of "placement new" safe, assuming that Foo in practice does not contain pointers (but might contain functions)?

Also, is the final delete call safe, and will it correctly call the destructor on the second "new" object, and correctly free the memory afterwards?

#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);
}

The simple example above compiles and runs without errors, but I cannot tell by running it whether it might blow up for some reason in a more complex environment.

3

There are 3 answers

1
Sergey Kalinichenko On BEST ANSWER

No, it is not dangerous to reuse memory of an object, provided that you are doing it correctly. Moreover, you do not have to restrict yourself to objects that have no pointers: by calling the destructor explicitly you can prepare the object for reuse, like this:

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
jrok On

It's safe because the object you're overwriting has a trivial destructor. From n3337, chapter 3.8 (Object lifetime):

4 A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression (5.3.5) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

The delete call is safe, too. You're calling it on a pointer that you got from new and there's a live object at that place.

And as you hinted in the question, it could invoke undefined behaviour if the destructor is non-trivial and has side effects - you need to call it explicitly in that case. Whether or not the class contains pointers is not directly important - reusing the storage is safe even in that case, but of course, you could introduce memory leaks and other bugs that way.

11
Matthieu M. On

There have been two answers already, but they give, I fear, an incomplete picture.

You may reuse the storage of an object, providing that you respect a few conditions:

  • You need not use a dynamically allocated object, any object is fine.
  • You should properly destroy the previous object, by calling its destructor (explicitly); failure to do so leads to undefined behavior if the destructor has side-effects (see §3.8/4)
  • The object that you place should have the same dynamic type as the previous object (see §3.8/7)

Let us review them, starting with any object is fine:

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{};
}

Let use continue with destroy the previous object:

struct Bar {
    int hello;
    std::string world;
};

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

And finally with same dynamic type:

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...
}