11

(5.3.4)

new-expression:

  • ::opt_new new-placement_opt new-type-id new-initializeropt

  • ::opt_new new-placement_opt ( type-id ) new-initializeropt

Entities created by a new-expression have dynamic storage duration (3.7.4). [ Note: the lifetime of such an entity is not necessarily restricted to the scope in which it is created. — end note ]

I think the following has 1 main object (local_object) with automatic storage duration, and 3 dummy classes with dynamic storage duration.

struct dummy
{
    int a;
};

char local_object[256];
dummy * a = new(&local_object) dummy;
dummy * b = new(&local_object +100) dummy;
dummy * c = new(&local_object +200) dummy;

The user @M.M. argues that there's only one object (local_object), and that the rest are just pointers. Is this correct?

(3.7)

The dynamic storage duration is associated with objects created with operator new

rici
  • 234,347
  • 28
  • 237
  • 341
Jts
  • 3,447
  • 1
  • 11
  • 14
  • `local_object`, `a`, `b` and `c` have automatic storage duration. `*a`, `*b` and `*c` don't. Btw, you should take care of alignment when using placement `new`. – 5gon12eder Feb 14 '16 at 08:28
  • It's just a random example tho. – Jts Feb 14 '16 at 08:29
  • What exactly is your question? The pointers will dangle, once `local_object` goes out of scope but the destructors of the placement-new'ed objects will have to be called manually. Or did you mean to ask something else? – 5gon12eder Feb 14 '16 at 08:34
  • 1
    Using memory like this is exactly the same as if you had just used the memory that regular old `new` gives you. Only difference is that you know where it is. – RamblingMad Feb 14 '16 at 08:35
  • 1
    @5gon12eder the question is whether `*a` has dynamic storage duration or automatic storage duration – M.M Feb 14 '16 at 08:35
  • 3
    a related question is whether `new(&local_object)` is considered as obtaining storage or not – M.M Feb 14 '16 at 08:36
  • Then the answer is “dynamic”, although it should not lead one into thinking that the pointer will be valid after `return`ing from that function. – 5gon12eder Feb 14 '16 at 08:37
  • 2
    Why is this even a question? `new` is `new`. Placement new doesn't make the objects placed any different; they still need to be destroyed using their destructor. – RamblingMad Feb 14 '16 at 08:38
  • 1
    @5gon12eder see [this program](http://ideone.com/ymQc24) - if the placement new gives dynamic storage duration then the second "destroy" should not appear (only automatic storage duration objects have their destructor run on a `}` being reached) – M.M Feb 14 '16 at 08:54
  • @M.M But that's a different story. In your program, `d` is a local variable with automatic storage duration just as `local_object` or `a` in the OP's case. Storage duration is determined at declaration time and doesn't change when you do stuff to the object later on. – 5gon12eder Feb 14 '16 at 09:10
  • 2
    @5gon12eder well that's what this question is about, does placement new make the object change to having dynamic storage duration? (the quotes provided in the question appear to say that it does) – M.M Feb 14 '16 at 09:14
  • @M.M The object doesn't "change". The original object (which had automatic storage duration) has been destroyed by virtue of its memory being re-used. Three new objects (with dynamic storage duration) are consecutively constructed in its place. – Lightness Races in Orbit Feb 14 '16 at 18:40
  • @M.M: I tried to cover the behaviour of your example program in my answer. IMO, the second destructor is being applied to the new object created on top of the original object, as allowed by 3.8/7, but not because the new object is automatic. Rather, it is because the destructor was scheduled to happen for the original (and new dearly-departed) automatic object, and the programmer has become responsible for ensuring that some appropriate object is in place to be the target of the pending destructor as per 3.8/8. – rici Feb 14 '16 at 18:40
  • @rici thanks, your answer seems to explain everything clearly – M.M Feb 14 '16 at 20:50

1 Answers1

9

It seems to me that the standard (as quoted in the OP) can only be interpreted as it reads, which is that operator new creates objects of dynamic storage duration, and this is true even if the underlying memory was obtained for an object of automatic duration.

This precise scenario is anticipated by the standard in §3.8 [basic.life] paragraph 8, with reference to the following example of undefined behaviour:

class T { };
struct B {
    ~B();
};
void h() {
    B b;
    new (&b) T;
}

The paragraph reads:

If a program ends the lifetime of an object of type T with static (3.7.1), thread (3.7.2), or automatic (3.7.3) storage duration and if T has a non-trivial destructor, the program must ensure that an object of the original type occupies that same storage location when the implicit destructor call takes place; otherwise the behavior of the program is undefined.

In the example, the program has "ended the lifetime" of the object b by reusing its storage, as provided by paragraph 4 of the same section: (emphasis added).

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.

In the example code, b's destructor was not called but this is acceptable because paragraph 4 explicitly allows a non-trivial destructor to not be called:

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;

as long as the program is prepared to live with the consequences of the destructor not being called.

But to return to paragraph 8, b's lifetime has ended, and the storage has been reused to create an object of type T. This object has dynamic storage duration, which means that its destructor will not be called implicitly. As above, it is not obligatory to explicitly call the destructor either, as long as the program does not require any side-effects which might be performed by the destructor.

Despite the fact that the lifetime of b has ended, the fact that b had automatic storage duration means that its destructor will be implicitly called when control flow leaves its scope. Calling a destructor on an object whose lifetime has ended is a specific case of the prohibition on using an object whose lifetime has ended, as per paragraph 6 of §3.8, which prohibits calling a non-static member member function of an object whose lifetime has ended but whose storage has not yet been reused or released.

For this reason, the example program's behaviour is undefined.

But paragraph 7 of this section provides a mechanism for the program to avoid the undefined behaviour by recreating a different object of the same type at the same location:

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

(7.1) — the storage for the new object exactly overlays the storage location which the original object occupied, and

(7.2) — the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and

(7.3) — the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and

(7.4) — the original object was a most derived object (1.8) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).

So, in my interpretation, the following snippet would have defined behaviour:

class T { };
struct B {
    ~B();
};
void h() {
    B b;
    new (&b) T;
    new (&b) B; /* recreate a B so that it can be destructed */
}

In short, the standard contemplates the possibility that an object of dynamic storage duration can be created using memory allocated to an object of automatic storage duration, and provides a set of restrictions and requirements for a well-defined program which performs this action, thereby avoiding the consequences of executing an implicit destructor on an object whose lifetime has been ended by reusing its storage.

Community
  • 1
  • 1
rici
  • 234,347
  • 28
  • 237
  • 341
  • So, we have 3.8\4: " _the destructor shall not be implicitly called_ " - and, 3.8\8: " _when the implicit destructor call takes place_ ". Wtf? – Eugene Zavidovsky Feb 14 '16 at 19:35
  • Oh, it seems you have to end the lifetime of _b_ by explicitly calling its destructor, and not by just reusing its storage; otherwise, there is undefined behaviour by 3.8\4, isn't there? – Eugene Zavidovsky Feb 14 '16 at 19:41
  • @eugene: The implicit destructor is not called _on the original object_ by virtue of the fact that its storage is reused. (Logical, because it is not easy to know that the storage is reused.) But the implicit destructor will be called, so there needs to be _another object_ in place at that point. – rici Feb 14 '16 at 19:42
  • I phucking love authors of CXX : ) – Eugene Zavidovsky Feb 14 '16 at 19:43
  • 1
    @eugene: with respect to your second comment, 3.8/4 explicitly says you don't need to call the destructor unless it performs side effects you rely on. How can you interpret that as saying that it requires an explicit call? With respect to your third comment, perhaps [tag:language-lawyer] is not for you :-) – rici Feb 14 '16 at 19:59
  • Oh, thanks! So, there would be not undefined behaviour, but a violation of this utterance in 3.8\4: " _however, if there is no explicit call to the destructor or if a delete-expression is not used to release the storage, **the destructor shall not be implicitly called**_ ". Am I right now? : > – Eugene Zavidovsky Feb 15 '16 at 12:07