End of lifetime

220 views
Skip to first unread message

razvyb...@gmail.com

unread,
Feb 9, 2018, 4:58:16 PM2/9/18
to ISO C++ Standard - Discussion
The first paragraph of [basic.life] says that (emphasis mine):

The lifetime of an object o of type T ends when:
  • if T is a class type with a non-trivial destructor ([class.dtor]), the destructor call starts, or
  • the storage which the object occupies is released, or is reused by an object that is not nested within o ([intro.object]).

But let us consider the following situation:


class B{
public:
    virtual void f(){ }
};

class D: public B{
public:
    virtual void f(){ }
};


D d;
B* pb = &d;
new (pb) B;

In [intro.object] it is said that a base class subobject is a subobject, and a subobject is considered to be nested.
Therefore the B class subobject is nested within the object "d", therefore the lifetime of "d" should not end.
However, the virtual table pointer of the B class subobject is now overwritten. Here is this exact example in action.

Did I miss anything from the standard?

Thank you.

Nicol Bolas

unread,
Feb 9, 2018, 5:08:30 PM2/9/18
to ISO C++ Standard - Discussion, razvyb...@gmail.com


On Friday, February 9, 2018 at 4:58:16 PM UTC-5, razvyb...@gmail.com wrote:
The first paragraph of [basic.life] says that (emphasis mine):

The lifetime of an object o of type T ends when:
  • if T is a class type with a non-trivial destructor ([class.dtor]), the destructor call starts, or
  • the storage which the object occupies is released, or is reused by an object that is not nested within o ([intro.object]).

But let us consider the following situation:


class B{
public:
    virtual void f(){ }
};

class D: public B{
public:
    virtual void f(){ }
};


D d;
B* pb = &d;
new (pb) B;

In [intro.object] it is said that a base class subobject is a subobject, and a subobject is considered to be nested.
Therefore the B class subobject is nested within the object "d", therefore the lifetime of "d" should not end.

Yes, `d::B` is nested within `d`. But remember what [basic.life] said: "reused by an object that is not nested with o".

While `d::B` is certainly nested within `d`, the new object you create with `new` is not, as defined in [intro.object]/4:

An object a is nested within another object b if:
- a is a subobject of b, or
- b provides storage for a, or
- there exists an object c where a is nested within c, and c is nested within b.

In this case, `d` does not "provide storage" for the object created by placement new (since `d` is not an array of bytes, it cannot "provide storage" for anything). Therefore, the storage for `d` is being reused, but it is not being reused by an object nested within it. And thus, `d`'s lifetime has ended.

razvyb...@gmail.com

unread,
Feb 9, 2018, 5:19:38 PM2/9/18
to ISO C++ Standard - Discussion, razvyb...@gmail.com
Thank you for your response.
So how can then an object that is nested within "o" reuse the storage of "o"?

razvyb...@gmail.com

unread,
Feb 9, 2018, 5:38:35 PM2/9/18
to ISO C++ Standard - Discussion, razvyb...@gmail.com
I thought of an example. 
Considering the following:

class A{};

class X{
   
unsigned char mem[sizeof(A)];
};

X x
;
new(x.mem) A;

The new A object is nested within x.mem because x.mem provides storage for it. Furthermore, x.mem is nested within x, as it is a class member. Therefore the new object is nested within x, as the "nesting" relation is transitive.

Is this correct? Are there any other examples?

Richard Smith

unread,
Feb 9, 2018, 8:09:15 PM2/9/18
to std-dis...@isocpp.org, razvyb...@gmail.com
On 9 February 2018 at 14:19, <razvyb...@gmail.com> wrote:
Thank you for your response.
So how can then an object that is nested within "o" reuse the storage of "o"?

That is described in [intro.object]p2:

"If an object is created in storage associated with a member subobject or array element e (which may or may not be within its lifetime), the created object is a subobject of e’s containing object if:
 — the lifetime of e’s containing object has begun and not ended, and
 — the storage for the new object exactly overlays the storage location associated with e, and
 — the new object is of the same type as e (ignoring cv-qualification)."

Note that this applies to member subobjects and array elements, but *not* to base class subobjects. The reason is that given in your question: in-place replacement of a base class subobject does not necessarily preserve the value of the enclosing complete object. (This is not limited to the vptr -- the tail padding of base class subobjects can be reused by derived classes, and when that happens, reinitializing a base class subobject can overwrite the value of derived class members.)
 

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.

razvyb...@gmail.com

unread,
Feb 10, 2018, 4:03:32 AM2/10/18
to ISO C++ Standard - Discussion, razvyb...@gmail.com
Ok, now I understand. So if a new object replaces some subobject of o under the conditions stated in [intro.object] p2, the new object becomes a subobject of o. Therefore it is nested within o. As such, the lifetime of o does not end, because its storage was reused by a new object that is nested within o. Thank you.

However, I have a related question. Does the standard say anything about the following situation?

class X{};

X x
;
new (&x) X;

Because [basic.life] p8 only covers cases when the lifetime of the old object already ended. I know that x's lifetime will end when its storage is reused by the new object, however this happens "at the same time" as the creation of the new object, not before, therefore I do not know whether [basic.life] p8 still applies.

To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.

Richard Smith

unread,
Feb 10, 2018, 6:50:55 PM2/10/18
to std-dis...@isocpp.org, razvyb...@gmail.com
On 10 February 2018 at 01:03, <razvyb...@gmail.com> wrote:
Ok, now I understand. So if a new object replaces some subobject of o under the conditions stated in [intro.object] p2, the new object becomes a subobject of o. Therefore it is nested within o. As such, the lifetime of o does not end, because its storage was reused by a new object that is nested within o. Thank you.

However, I have a related question. Does the standard say anything about the following situation?

class X{};

X x
;
new (&x) X;

Because [basic.life] p8 only covers cases when the lifetime of the old object already ended. I know that x's lifetime will end when its storage is reused by the new object, however this happens "at the same time" as the creation of the new object, not before, therefore I do not know whether [basic.life] p8 still applies.

It was my intent that the rule would apply here (in the context of working on P0137). I can't say for sure that the other people examining the wording change understood that to be the intent, but I think it's really the only natural way to combine the "lifetime ends when storage is reused" rule and the "storage reuse of the same type allows the old name to be used to access the new object" rule.

Another way of looking at the rule in p8 is that (excluding the weird const / reference cases) accessing an object only cares that there is *an* object of the correct most-derived type in the storage, and does not care whether it's the same object. (Although forming the access path itself may impose additional requirements.)

Perhaps we could clarify this by changing the "The lifetime of an object o of type T ends when" in p1 to say something like "[...] ends immediately before".
 
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.

razvyb...@gmail.com

unread,
Feb 10, 2018, 7:03:45 PM2/10/18
to ISO C++ Standard - Discussion, razvyb...@gmail.com
Yes, if it does not affect anything else in the standard, I believe the modification that you propose would clarify this situation.
Hopefully just one last question, what exactly are you referring to by "(Although forming the access path itself may impose additional requirements.)"? Is it related to how you must use "std::launder" in special cases?

Thank you very much for having the patience to answer my questions.

Richard Smith

unread,
Feb 11, 2018, 1:07:21 AM2/11/18
to std-dis...@isocpp.org, Maris Razvan
On 10 February 2018 at 16:03, <razvyb...@gmail.com> wrote:
Yes, if it does not affect anything else in the standard, I believe the modification that you propose would clarify this situation.
Hopefully just one last question, what exactly are you referring to by "(Although forming the access path itself may impose additional requirements.)"? Is it related to how you must use "std::launder" in special cases?

What I mean is for cases like this:

struct A { int x, y; };
int arr[2];
new (&arr) A;
arr[1] = 0; // UB even though there is an int at this location because the array indexing requires an in-lifetime array object

We (probably, depending on struct layout) created two new int objects in the storage of the two int elements of the array, but you can't use array indexing on 'arr' to get at them because the array no longer exists.
 
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.

razvyb...@gmail.com

unread,
Feb 11, 2018, 5:02:27 AM2/11/18
to ISO C++ Standard - Discussion, razvyb...@gmail.com
The example that you showed is UB because it violates [basic.life] p7.1, correct?
 
[...] the glvalue is used to access the object [...]
 
However, p8 assures us that the following is legal (given that indeed the creation of A causes a new "int" object to be created in place of arr[1]), am I right?

int* p = &(arr[1]);
new (&arr) A;
*p = 0;


razvyb...@gmail.com

unread,
Feb 16, 2018, 3:17:21 AM2/16/18
to ISO C++ Standard - Discussion, razvyb...@gmail.com
I apologize for insisting, I want to make sure I understand these rules. Were my previous assumptions correct? Thank you.

Richard Smith

unread,
Feb 16, 2018, 4:29:12 PM2/16/18
to std-dis...@isocpp.org, Maris Razvan
On 16 February 2018 at 00:17, <razvyb...@gmail.com> wrote:
I apologize for insisting, I want to make sure I understand these rules. Were my previous assumptions correct? Thank you.


On Sunday, February 11, 2018 at 12:02:27 PM UTC+2, razvyb...@gmail.com wrote:
The example that you showed is UB because it violates [basic.life] p7.1, correct?
 
[...] the glvalue is used to access the object [...]
 
However, p8 assures us that the following is legal (given that indeed the creation of A causes a new "int" object to be created in place of arr[1]), am I right?

int* p = &(arr[1]);
new (&arr) A;
*p = 0;

Yes, as far as I can see, this is valid under the current rules. (However, it's still in an area that I would consider "dubious", and I wouldn't be surprised if some compilers "optimize" it in undesirable ways.)
 
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.

razvyb...@gmail.com

unread,
Feb 16, 2018, 5:25:59 PM2/16/18
to ISO C++ Standard - Discussion, razvyb...@gmail.com
Ok, I understand. Thank you very much for your help!

Language Lawyer

unread,
Sep 15, 2018, 7:52:17 PM9/15/18
to ISO C++ Standard - Discussion, razvyb...@gmail.com
On Sunday, February 11, 2018 at 2:50:55 AM UTC+3, Richard Smith wrote:
Perhaps we could clarify this by changing the "The lifetime of an object o of type T ends when" in p1 to say something like "[...] ends immediately before".

I think it would be better to clarify when the reuse of a storage begins.
For objects with constructors, I suspect it is started to be reused when the execution of a constructor starts.
Reply all
Reply to author
Forward
0 new messages