Discussion:
object lifetime, storage duration, and destructors
(too old to reply)
Viacheslav Usov
2016-08-10 15:29:59 UTC
Permalink
In a recent thread, or even threads, we had a discussion essentially about
what "storage duration" really means when "object lifetime" ends.

It should be pointed out that in the C language, both terms mean the same
thing. To quote:

6.2.4 Storage durations of objects


1 An object has a storage duration that determines its lifetime. There are
four storage durations: static, thread, automatic, and allocated. Allocated
storage is described in 7.22.3.


2 The lifetime of an object is the portion of program execution during
which storage is guaranteed to be reserved for it. An object exists, has a
constant address,33) and retains its last-stored value throughout its
lifetime.34) If an object is referred to outside of its lifetime, the
behavior is undefined. The value of a pointer becomes indeterminate when
the object it points to (or just past) reaches the end of its lifetime.


Reading that, I feel that the quality of that exposition is greatly
superior to what we have in the C++ standard. Just take this one sentence:
"An object exists, has a constant address,33) and retains its last-stored
value throughout its lifetime.34)". Instead, we have this in the C++
standard: "The properties ascribed to objects and references throughout
this International Standard apply for a given object or reference only
during its lifetime." (3.8.4). Does that mean "we cannot even say whether
its last-stored value is retained"?

Anyway, the good thing is that at least for vacuously-initialised
trivially-destructible objects both terms seem to mean the same thing even
in C++. Which is good, because plain C structures are trivially
destructible in C++ and can be vacuously initialised in C++, so the
intended behaviour is *probably* still identical in C and C++.

Things get messier when trivially-destructible objects are members or
bases, because then we have this:

[class.cdtor] For an object with a non-trivial destructor, referring to any
non-static member or base class of the object after the destructor finishes
execution results in undefined behaviour.


So if a derived or enclosing class has a non-trivial destructor, accessing
trivially-destructible members or bases after destruction, even though they
seem to have continued their lifetimes, is undefined behaviour. That may be
one reason why "we cannot even say whether its last-stored value is
retained".

Before I say anything else, I find it very unfortunate that [class.cdtor]
(as quoted above) and this

[class.dtor] Once a destructor is invoked for an object, the object no
longer exists; the behavior is undefined if the destructor is invoked for
an object whose lifetime has ended.


and probably other sections specify the behaviour in the situations that
are also covered by [basic.life]. It is hard to say whether what they try
to say together is really a coordinated effort or just an accident, and it
is very hard to say whether this is really *all* the standard has to say on
those situations. No matter what I say in the following, I think this is
something that needs an improvement.

One particular opinion on this was summarised by Richard Smith: "we
currently seem to fail to say that the lifetime of a member object ends
when the lifetime of the enclosing object ends, if the member object has a
trivial destructor. That seems like a defect to me." The "defect" sentiment
seems to be shared, if not universally, then significantly, by other
commentators. If the "defect" is corrected as implied by Richard's
statement, then "we cannot even say whether its last-stored value is
retained" is no longer true (at least till we end up in yet another dark
corner).

Yet, what do we really gain by making the lifetime end sooner (beyond
fixing what seems to be an inconsistency)? Why can't we fix the
inconsistency by making it *last longer?* For example, we could modify this:

For an object with a non-trivial destructor, referring to any non-static
member or base class of the object after the destructor finishes execution
results in undefined behaviour.


to something along these lines:

Referring to any virtual member or virtual base class of the object after
the destructor finishes execution results in undefined behaviour. The
execution of a destructor shall not have any side effects with respect to
data members and non-virtual base classes except those produced by the
execution of its body and the bodies of the member and base destructors. [
Note: these side effects may still result in undefined or
implementation-defined behavior when a data member or a non-virtual base
class is referred to after the destructor finishes execution. --end note ]


The basic idea above is that the implementation is unconstrained in its
magic where it is required by C++ (virtual members and bases), but all data
(not necessarily POD, trivial or trivially destructible) members and
non-virtual bases are (in principle) fully controllable by the programmer,
thus not sacrificing anything in comparison with C, and fixing "we cannot
even say whether its last-stored value is retained".

That will, obviously, need additional changes in [basic.life] to make it
fully usable, which I'm not mentioning right now.

Example:

struct A
{
int a;
char *p;

A(): a(1), p(new char[16]) {}
~A() { delete p; }
};

char buf[sizeof (A)];
auto pa = new (buf) A;
pa->~A();
auto a = pa->a; // well-defined, a == 1
auto p = pa->p; // UB

Are there any reasons why this would be undesirable?

I anticipate one such reason, that this would make it impossible for
implementations to modify the memory of the destructed object to detect
use-after-deletion bugs. But, as the name use-after-deletion suggests, such
modification ought to be performed after deletion, i.e., when storage
duration ends, not when destructing the object, so the technique would
still be possible.

Cheers,
V.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Thiago Macieira
2016-08-11 01:37:22 UTC
Permalink
Post by Viacheslav Usov
One particular opinion on this was summarised by Richard Smith: "we
currently seem to fail to say that the lifetime of a member object ends
when the lifetime of the enclosing object ends, if the member object has a
trivial destructor. That seems like a defect to me." The "defect" sentiment
seems to be shared, if not universally, then significantly, by other
commentators. If the "defect" is corrected as implied by Richard's
statement, then "we cannot even say whether its last-stored value is
retained" is no longer true (at least till we end up in yet another dark
corner).
I think we should be able to say that the last stored value is retained in any
storage. The problem is that we can't tell what the act of destruction may
store. So it retains something, you just don't know what.
Post by Viacheslav Usov
Yet, what do we really gain by making the lifetime end sooner (beyond
fixing what seems to be an inconsistency)? Why can't we fix the
Why would a part of an object be allowed to outlive the object that contains
it? That can't exist in C either: you can't partially free a pointer,
partially destroy an automatic, thread or static variable. In C, either the
whole object is alive or no part of it is.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Viacheslav Usov
2016-08-11 13:17:39 UTC
Permalink
Post by Thiago Macieira
I think we should be able to say that the last stored value is retained
in any storage. The problem is that we can't tell what the act of
destruction may store. So it retains something, you just don't know what.

When a member function is called, we may not know what it stores. The
standard, however, does not say that accessing a member after any member
function is called results in UB. But it says that when the member function
is a non-trivial destructor.

Even an object with virtual members or bases can still have a trivial
destructor, in which case the standard says no UB should result when
accessing members post-destruction.

Thus, everything *except* the user-provided code in a destructor is not
seen by the standard as something that may result in UB when accessing a
member post-destruction. What is the difference between the user-provided
code in any member function and in a destructor? None. Why should even an*
empty* user-provided destructor need to be decorated with UB in the
standard?

The more I think about it, the less sense this all makes to me.
Post by Thiago Macieira
Why would a part of an object be allowed to outlive the object that
contains it?

I did not propose that. What I did consider was that we should extend the
lifetime of data members and the complete object for the rest of their
storage durations (which are equal).

Cheers,
V.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Thiago Macieira
2016-08-11 16:35:14 UTC
Permalink
Post by Viacheslav Usov
Post by Thiago Macieira
I think we should be able to say that the last stored value is retained
in any storage. The problem is that we can't tell what the act of
destruction may store. So it retains something, you just don't know what.
When a member function is called, we may not know what it stores. The
standard, however, does not say that accessing a member after any member
function is called results in UB. But it says that when the member function
is a non-trivial destructor.
That's not what I meant. I meant the destruction itself, the } of the last
destructor. Even if you have an empty, inline destructor, if it's not trivial,
that destructor could store something.
Post by Viacheslav Usov
Even an object with virtual members or bases can still have a trivial
destructor, in which case the standard says no UB should result when
accessing members post-destruction.
No, it can't. Such destructors are not trival.
Post by Viacheslav Usov
Thus, everything *except* the user-provided code in a destructor is not
seen by the standard as something that may result in UB when accessing a
member post-destruction. What is the difference between the user-provided
code in any member function and in a destructor? None. Why should even an*
empty* user-provided destructor need to be decorated with UB in the
standard?
Because this allows the compiler two optimisations:
1) assume that no one is going to read values after the end of the lifetime
of the object, so it need not store things it can prove will not be read

2) reuse the storage for its own purposes, once it knows the object that used
to life there is dead

We have proof that GCC is already doing #1. It does that the same way that it
eliminates stores to any other variable that it knows won't be read (cf the
memset case of a buffer in automatic storage before the function returns).
Post by Viacheslav Usov
The more I think about it, the less sense this all makes to me.
Post by Thiago Macieira
Why would a part of an object be allowed to outlive the object that
contains it?
I did not propose that. What I did consider was that we should extend the
lifetime of data members and the complete object for the rest of their
storage durations (which are equal).
That doesn't make sense. The lifetime of an object ends when its destructor is
run. But the storage is allowed outlive the object, see std::aligned_storage
and std::vector.

To do otherwise would mean that you can still call member functions and access
data members, all the while assuming that the invariants are still valid.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Viacheslav Usov
2016-08-12 09:28:47 UTC
Permalink
Post by Thiago Macieira
That's not what I meant. I meant the destruction itself, the } of the
last destructor.

What you really meant is that the standard stipulates that accessing a
member after a non-trivial destructor has begun results in undefined
behaviour. That's why it can store anything. I know this.

I'm not sure whether you really read my original message, you seem to be
responding to some sentences taken out of context. I know what the standard
says; I take issue with what it says, and you are trying to justify what it
says by essentially repeating what it says. I do not think that's helpful
at all.
Post by Thiago Macieira
No, it can't. Such destructors are not trival.
You are mistaken.

A destructor is trivial if it is not user-provided and if:

(5.4) — the destructor is not virtual,
(5.5) — all of the direct base classes of its class have trivial
destructors, and
(5.6) — for all of the non-static data members of its class that are of
class type (or array thereof), each such class has a trivial destructor.

Otherwise, the destructor is non-trivial.

[class.dtor]


Nothing is said about virtual members (other than the destructor) and
virtual bases, hence a class with virtual members and virtual bases can
have a trivial destructor, just like I said.
Post by Thiago Macieira
1) assume that no one is going to read values after the end of the
lifetime of the object, so it need not store things it can prove will not
be read

Hmm, OK, that sounds plausible.
Post by Thiago Macieira
2) reuse the storage for its own purposes, once it knows the object that
used to life there is dead

This I find a lot more questionable.
Post by Thiago Macieira
That doesn't make sense. The lifetime of an object ends when its
destructor is run. But the storage is allowed outlive the object, see
std::aligned_storage and std::vector. To do otherwise would mean that you
can still call member functions and access data members, all the while
assuming that the invariants are still valid.

That makes perfect sense and is already the case for objects with trivial
destructors. The term "invariant" is not defined and not used by the
standard with respect to the core language.

Cheers,
V.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Thiago Macieira
2016-08-12 16:24:47 UTC
Permalink
Post by Viacheslav Usov
Post by Thiago Macieira
2) reuse the storage for its own purposes, once it knows the object that
used to life there is dead
This I find a lot more questionable.
Why? If the compiler has access to storage that it knows no one else is using,
why couldn't it use it for its own purposes?
Post by Viacheslav Usov
Post by Thiago Macieira
That doesn't make sense. The lifetime of an object ends when its
destructor is run. But the storage is allowed outlive the object, see
std::aligned_storage and std::vector. To do otherwise would mean that you
can still call member functions and access data members, all the while
assuming that the invariants are still valid.
That makes perfect sense and is already the case for objects with trivial
destructors. The term "invariant" is not defined and not used by the
standard with respect to the core language.
Trivial destructors mean that the object simply cannot be destroyed before the
storage itself gets reused for other purposes. But if you add a non-trivial
destructor in the mix, then the lifetime ends, even if the storage is still
accessible.

"Invariant" is not a language term, agreed, but it's something that you and I
should be able to agree on. For example, a smart pointer can leave the pointer
value in the storage area after that pointer was free()d. You can't
dereference that pointer because the invariant guarantees of the smart pointer
are no longer applicable.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Viacheslav Usov
2016-08-12 17:29:07 UTC
Permalink
Post by Thiago Macieira
Why? If the compiler has access to storage that it knows no one else is
using, why couldn't it use it for its own purposes?

If you phrase it like this, the answer is "sure". But, again, you're
phrasing it like this because you assume that post-destruction access is
UB, so the compiler can indeed do what it wants with that storage. My point
is that I do not assume that having that UB is a good thing; then the two
optimizations you mentioned *might* be a way to justify the UB, but not the
other way around. And this second optimisation, where a compiler can store
something there, does not strike me as particularly useful. When do you
think that could be beneficial?
Post by Thiago Macieira
Trivial destructors mean that the object simply cannot be destroyed
before the storage itself gets reused for other purposes. But if you add a
non-trivial destructor in the mix, then the lifetime ends, even if the
storage is still accessible.

Once again, I do not need to be told that. I know that, and I have even
quoted some of that in the very first message in this thread. I just do not
see why the standard needs to be like that. Describing what the standard
says is not helpful for that.
Post by Thiago Macieira
"Invariant" is not a language term, agreed, but it's something that you
and I should be able to agree on. For example, a smart pointer can leave
the pointer value in the storage area after that pointer was free()d. You
can't dereference that pointer because the invariant guarantees of the
smart pointer are no longer applicable.

This goes back to your first message in this thread. The core language does
not require any invariants to be held in any given object. The state you
just described can occur in any object at any time. Destructors are not
special in this respect.

Cheers,
V.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Thiago Macieira
2016-08-12 17:48:43 UTC
Permalink
Post by Viacheslav Usov
Post by Thiago Macieira
Why? If the compiler has access to storage that it knows no one else is
using, why couldn't it use it for its own purposes?
If you phrase it like this, the answer is "sure". But, again, you're
phrasing it like this because you assume that post-destruction access is
UB, so the compiler can indeed do what it wants with that storage. My point
is that I do not assume that having that UB is a good thing; then the two
optimizations you mentioned *might* be a way to justify the UB, but not the
other way around. And this second optimisation, where a compiler can store
something there, does not strike me as particularly useful. When do you
think that could be beneficial?
Many ABIs require passing an implicit parameter pointing to a storage area so
that oversized objects can be returned. Usually, the compiler finds room in the
stack to do that. Having access to another storage area means that it
decreases the stack pressure in calling functions inside the destructor.

This could happen when the destructors of two sub-objects have been inlined in
a larger object: the compiler has access to the already-destroyed one's
storage information.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
'Edward Catmur' via ISO C++ Standard - Discussion
2016-08-12 19:37:26 UTC
Permalink
Post by Thiago Macieira
Post by Viacheslav Usov
Post by Thiago Macieira
Why? If the compiler has access to storage that it knows no one else is
using, why couldn't it use it for its own purposes?
If you phrase it like this, the answer is "sure". But, again, you're
phrasing it like this because you assume that post-destruction access is
UB, so the compiler can indeed do what it wants with that storage. My
point
Post by Viacheslav Usov
is that I do not assume that having that UB is a good thing; then the two
optimizations you mentioned *might* be a way to justify the UB, but not
the
Post by Viacheslav Usov
other way around. And this second optimisation, where a compiler can
store
Post by Viacheslav Usov
something there, does not strike me as particularly useful. When do you
think that could be beneficial?
Many ABIs require passing an implicit parameter pointing to a storage area so
that oversized objects can be returned. Usually, the compiler finds room in the
stack to do that. Having access to another storage area means that it
decreases the stack pressure in calling functions inside the destructor.
This could happen when the destructors of two sub-objects have been inlined in
a larger object: the compiler has access to the already-destroyed one's
storage information.
That's a pretty decent motivation, thanks. Would I be wrong in thinking
that it would also apply to a constructor - the compiler could use a later
subobject as scratch space for calling functions to initialize an earlier
subobject? Because in that case code running prior to or during the
constructor would also not be guaranteed to observe zeroes for a complete
object with static storage duration, nor to have modifications persist into
the lifetime of the complete object.

I see a couple of related questions here:

1. After an object (not necessarily a complete object) with static storage
duration has begun initialization but before its constructor is entered,
can its storage be observed to be zero (noting that its primitive
subobjects have already begun their lifetime and been zero-initialized)?
2. After an object has begun initialization but before its constructor is
entered, can its storage be modified (noting that its primitive subobjects
have already begun their lifetime and that lifetime will persist through
the beginning of the constructor)?
3. If so, are any modifications guaranteed to persist up to the point the
constructor is entered?
4. After the destructor of an object has finished but before the destructor
of its complete object finishes, can its storage be modified (noting that
the lifetime of its primitive subobjects continues)?
5. In any case, after the destructor of its complete object finishes, can
its storage be observed to hold the same bytes as it did just prior to the
destructor of the subobject finishing?

If the answer to all of these questions is "no" - and I appreciate that
there are good reasons it should be - what changes to the language would be
necessary to make this unambiguous? I would consider something like the
following to be sufficient:

* For an object with a non-trivial constructor, after the initialization of
its complete object has begun [ *Note:* including zero-initialization *--
end note* ] but before the constructor begins execution, accessing the
storage of the object or accessing any nested object [ *Note:* for example,
by using std::memcpy or std::launder *-- end note* ] results in undefined
behavior.
* For an object with non-trivial destructor, after the destructor finishes
execution but before the destructor of its complete object finishes
execution, accessing the storage of the object or accessing any nested
object results in undefined behavior. After the destructor of its complete
object finishes execution but before storage is released, any nested object
whose lifetime was not ended by the destructor holds an indeterminate value.

Anyone with me on this?
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Thiago Macieira
2016-08-12 20:03:32 UTC
Permalink
On sexta-feira, 12 de agosto de 2016 20:37:26 PDT 'Edward Catmur' via ISO C++
Post by 'Edward Catmur' via ISO C++ Standard - Discussion
Post by Thiago Macieira
Many ABIs require passing an implicit parameter pointing to a storage area so
that oversized objects can be returned. Usually, the compiler finds room in the
stack to do that. Having access to another storage area means that it
decreases the stack pressure in calling functions inside the destructor.
This could happen when the destructors of two sub-objects have been inlined in
a larger object: the compiler has access to the already-destroyed one's
storage information.
That's a pretty decent motivation, thanks. Would I be wrong in thinking
that it would also apply to a constructor - the compiler could use a later
subobject as scratch space for calling functions to initialize an earlier
subobject? Because in that case code running prior to or during the
constructor would also not be guaranteed to observe zeroes for a complete
object with static storage duration, nor to have modifications persist into
the lifetime of the complete object.
Yes, all that we are talking about here for destruction has a dual in
construction, but it needs to obey the rule of zero-initialisation of static-
lifetime objects: the end result needs to be zero of anything that wasn't
explicitly written to.
Post by 'Edward Catmur' via ISO C++ Standard - Discussion
1. After an object (not necessarily a complete object) with static storage
duration has begun initialization but before its constructor is entered,
can its storage be observed to be zero (noting that its primitive
subobjects have already begun their lifetime and been zero-initialized)?
2. After an object has begun initialization but before its constructor is
entered, can its storage be modified (noting that its primitive subobjects
have already begun their lifetime and that lifetime will persist through
the beginning of the constructor)?
3. If so, are any modifications guaranteed to persist up to the point the
constructor is entered?
Good questions. I was writing the same questions myself when I noticed you had
asked them too.
Post by 'Edward Catmur' via ISO C++ Standard - Discussion
4. After the destructor of an object has finished but before the destructor
of its complete object finishes, can its storage be modified (noting that
the lifetime of its primitive subobjects continues)?
And noting that I dispute that primitive subobjects' lifetime continues.
Post by 'Edward Catmur' via ISO C++ Standard - Discussion
5. In any case, after the destructor of its complete object finishes, can
its storage be observed to hold the same bytes as it did just prior to the
destructor of the subobject finishing?
Good question.
Post by 'Edward Catmur' via ISO C++ Standard - Discussion
If the answer to all of these questions is "no" - and I appreciate that
there are good reasons it should be - what changes to the language would be
necessary to make this unambiguous? I would consider something like the
* For an object with a non-trivial constructor, after the initialization of
its complete object has begun [ *Note:* including zero-initialization *--
end note* ] but before the constructor begins execution, accessing the
storage of the object or accessing any nested object [ *Note:* for example,
by using std::memcpy or std::launder *-- end note* ] results in undefined
behavior.
* For an object with non-trivial destructor, after the destructor finishes
execution but before the destructor of its complete object finishes
execution, accessing the storage of the object or accessing any nested
object results in undefined behavior. After the destructor of its complete
object finishes execution but before storage is released, any nested object
whose lifetime was not ended by the destructor holds an indeterminate value.
Anyone with me on this?
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Edward Catmur
2016-08-11 17:21:25 UTC
Permalink
Post by Viacheslav Usov
Post by Viacheslav Usov
One particular opinion on this was summarised by Richard Smith: "we
currently seem to fail to say that the lifetime of a member object ends
when the lifetime of the enclosing object ends, if the member object has
a
Post by Viacheslav Usov
trivial destructor. That seems like a defect to me." The "defect"
sentiment
Post by Viacheslav Usov
seems to be shared, if not universally, then significantly, by other
commentators. If the "defect" is corrected as implied by Richard's
statement, then "we cannot even say whether its last-stored value is
retained" is no longer true (at least till we end up in yet another dark
corner).
I think we should be able to say that the last stored value is retained in any
storage. The problem is that we can't tell what the act of destruction may
store. So it retains something, you just don't know what.
Post by Viacheslav Usov
Yet, what do we really gain by making the lifetime end sooner (beyond
fixing what seems to be an inconsistency)? Why can't we fix the
inconsistency by making it *last longer?* For example, we could modify
Why would a part of an object be allowed to outlive the object that contains
it? That can't exist in C either: you can't partially free a pointer,
partially destroy an automatic, thread or static variable. In C, either the
whole object is alive or no part of it is.
Actually, subobjects already outlive their containing object. For an object
o of type T with non-trivial destructor, the lifetime of o ends when the
destructor of T *starts*, however we certainly can access base and member
subobjects up until the destructor of T *finishes*. So at least within the
destructor of T we can observe o.i to be alive and o to be dead. This also
holds within the non-delegating constructor of o.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Viacheslav Usov
2016-08-11 12:23:17 UTC
Permalink
Post by Viacheslav Usov
Yet, what do we really gain by making the lifetime end sooner (beyond
fixing what seems to be an inconsistency)? Why can't we fix the
inconsistency by making it *last longer?*

There was one particular motivating example that I had in mind, and which I
forgot once I got to the middle of my message. Here it is:

struct A
{
int a;
A() : a(1) {}
};

char buf[sizeof (A)];
auto pa = new (buf) A;
pa->~A();
auto a = pa->a; // ???

According to [basic.life], the lifetime of the object pointed to by pa has
not ended when the destructor was called, because the destructor is
trivial. According to [class.cdtor], referring to its member a should not
result in undefined behaviour for the same reason. One could then argue
that the value of a should be 1. According to [class.dtor], however, "the
object no longer exists", but what that means beyond undefined behaviour
when invoking the destructor again is not specified; one could argue that
this conflicts with the [basic.life], where the lifetime has not ended yet;
it is possible that "the object no longer exists" is simply wrong and
should be removed (it is interesting, though, that the conflict has existed
since C++98) .

Now, adding seemingly benign ~A() {} to the definition of A has a major
impact, according to both [basic.life] and [class.cdtor]: we should have
undefined behaviour when member a is accessed, and even [class.dtor] sort
of makes sense.

I find it very surprising that an empty non-virtual destructor should have
such a major side effect.

Cheers,
V.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Continue reading on narkive:
Loading...