Search code examples
c++language-lawyerundefined-behaviorobject-lifetimeplacement-new

Why isn't it undefined behaviour to destroy an object that was overwritten by placement new?


I'm trying to figure out whether the following is undefined behaviour. I have a feeling it's not UB, but my reading of the standard makes it look like it is UB:

#include <iostream>

struct A {
    A() { std::cout << "1"; }
    ~A() { std::cout << "2"; }
};

int main() {
    A a;
    new (&a) A;
}

Quoting the C++11 standard:

basic.life¶4 says "A program may end the lifetime of any object by reusing the storage which the object occupies"

So after new (&a) A, the original A object has ended its lifetime.

class.dtor¶11.3 says that "Destructors are invoked implicitly for constructed objects with automatic storage duration ([basic.stc.auto]) when the block in which an object is created exits ([stmt.dcl])"

So the destructor for the original A object is invoked implicitly when main exits.

class.dtor¶15 says "the behavior is undefined if the destructor is invoked for an object whose lifetime has ended ([basic.life])."

So this is undefined behaviour, since the original A no longer exists (even if the new a now exists in the same storage).

The question is whether the destructor for the original A is called, or whether the destructor for the object currently named a is called.

I am aware of basic.life¶7, which says that the name a refers to the new object after the placement new. But class.dtor¶11.3 explicitly says that it's the destructor of the object which exits scope which is called, not the destructor of the object referred to by a name that exits scope.

Am I misreading the standard, or is this actually undefined behaviour?

Edit: Several people have told me not to do this. To clarify, I'm definitely not planning on doing this in production code! This is for a CppQuiz question, which is about corner cases rather than best practices.


Solution

  • Am I misreading the standard, or is this actually undefined behaviour?

    None of those. The standard is not unclear but it could be clearer. The intent though is that the new object's destructor is called, as implied in [basic.life]p9.

    [class.dtor]p12 isn't very accurate. I asked Core about it and Mike Miller (a very senior member) said:

    I wouldn't say that it's a contradiction [[class.dtor]p12 vs [basic.life]p9], but clarification is certainly needed. The destructor description was written slightly naively, without taking into consideration that the original object occupying a bit of automatic storage might have been replaced by a different object occupying that same bit of automatic storage, but the intent was that if a constructor was invoked on that bit of automatic storage to create an object therein - i.e., if control flowed through that declaration - then the destructor will be invoked for the object presumed to occupy that bit of automatic storage when the block is exited - even it it's not the "same" object that was created by the constructor invocation.

    I'll update this answer with the CWG issue as soon as it is published. So, your code does not have UB.