C++ Logo

std-proposals

Advanced search

[std-proposals] Fwd: Relax condition for potentially invoked destructor in constructor

From: Jason McKesson <jmckesson_at_[hidden]>
Date: Sat, 26 Feb 2022 10:30:25 -0500
---------- Forwarded message ---------
From: Jason McKesson <jmckesson_at_[hidden]>
Date: Sat, Feb 26, 2022 at 10:29 AM
Subject: Re: [std-proposals] Relax condition for potentially invoked
destructor in constructor
To: organicoman <organicoman_at_[hidden]>


On Sat, Feb 26, 2022 at 3:27 AM organicoman <organicoman_at_[hidden]> wrote:
>
> To Arvind,
> This is what i've send you.
> "An object at construction does not call its destructor, in case of exception or error.
> A destructor exists only if the instantiated object had successfully been constructed."
>
> And the following is its detailed explanation.
>
> An object is only considered to have started its lifetime when the
> (non-delegating) constructor for that object returns. In a
> constructor, base classes are initialized first (declaration order),
> then members (also declaration order), and finally the body of the
> constructor. Only when an object is constructed successfully can it be
> destroyed.
>
> If a member's initialization fails via an exception, then the object
> that it was a member of was *never constructed*. Its destructor does
> not get called because its constructor never concluded, let alone
> began. This means that the outer object never really existed.
>
> A destructor only gets called on objects that were successfully
> constructed. In the case of a member's initialization throwing an
> exception, any members & base classes that were successfully
> constructed up to that point are destroyed, in reverse declaration
> order.
>
> So let's thank Jason for his time to write all this.
>
> Now to Jason,
>
> Your ultimate point, that each constructor currently already needs to
> keep track of which objects have been constructed, is wrong. Because
> each constructor initializes subobjects in the same order, any
> particular constructor only has to keep track of how many subobjects
> have been initialized before the exception. When the exception
> happens, it can simply jump to the destruction code for destroying all
> prior objects. Because they all share the same construction ordering,
> they all share the same destruction ordering too.
>
> That was not my ultimate point,
> I used the idea of "keep track", to explain to others that in certain way we already know from where to start destroying our object under construction.
>
> What you want would require each constructor to have its own set of
> destruction orderings to match their separate construction ordering.
>
> No! I'm not saying that.
>
> Plus, you turn a very simple rule (subobjects are initialized in
> declaration order) into a complex one (subobjects are initialized
> maybe in declaration order and maybe not, depending on the
> constructor).
>
> Yes it is a simple idea, and its implementation is lazy.
> First of all, let me explain what i mean by keep track.
> 1- we should understand that member initialization list i.e subobject members construction, is just a list of calls to constructors.
> 2- constructors are simply functions, most of them are inlined, few of them are actual function calls.
> 3- calling these functions (i.e member constructors) in the context of constructing the object, is creating a stack frame.
> 4- raising an exception during this process, is just a matter of unwinding that stack frame. call it back tracking.
>
> So physically we are using the "Stack Segment" to keep track of what was successfully constructed in the list of subobject members, right! LIFO

That's not how constructors work. No "stack segment" is used to keep
track of anything. It's just a sequence of opcodes in assembly. If an
exception is caught in a particular place, it jumps to the routine
that destroys the subobjects. Exactly where it jumps to depends on
where it caught the exception.

Given a type with members X, Y, and Z, the way it works is like this:

```
//Constructor 1:
X()
if(exception) return;
Y()
if(exception) goto Y_fail;
Z()
if(exception) goto Z_fail;
Constructor body
if(exception) goto Body_fail;

//Constructor 2:
X(...)
if(exception) return;
Y(...)
if(exception) goto Y_fail;
Z(...)
if(exception) goto Z_fail;
Constructor body
if(exception) goto Body_fail;

//Destructor
Destructor body
Body_fail:
~Z()
Z_fail: //Z was never constructed, so we don't need to destroy it.
~Y()
Y_fail:
~X()
return
```

Notice how both constructors are jumping to the *same set* of
destruction functionality. This works because they initialize members
in the same order.

If they initialized members in a different order, they would *have to*
destroy them in the reverse of that order. So each constructor would
have to have its own destruction order in addition to the destructor's
destruction order.

Received on 2022-02-26 15:30:33