C++ Logo

std-proposals

Advanced search

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

From: organicoman <organicoman_at_[hidden]>
Date: Sat, 26 Feb 2022 02:36:35 +0400
Connor, ArvindAn 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. Look at this example: (pay attention to the member "TheFree d" of the class "test")#include <iostream>struct foo{ int i; foo()=delete; foo(int _i): i(_i){} ~foo(){puts("foo::dtor");}};struct bar{ foo f; bar()=delete; bar(foo _f): f(_f) {} ~bar(){puts("bar::dtor");}};int Throw(){1/0;}struct TheFree{ int* p; TheFree(): p(new int(5)){} ~TheFree(){ delete p;puts("called delete");}};struct boom{ int b; boom()=delete; boom(int _b): b(_b){}};struct test{ int a; foo f; boom o; bar b; TheFree d; test()=delete; //test(int _a): // b(f), f(a), a(22), o(1), d(){} test(int _a):a(_a), f(a), o(Throw()), b(f), d() {}};int main(){    test t(4);    puts("-----");    return 0;}    // print the log of t's destructionOutput:foo::dtor-----called deletebar::dtorfoo::dtorfoo::dtorObserve that only "foo", dtor had been called because it is the only member object fully constructed before the exception (div by zero)This is the exact illustration of the standard rule of (potentially constructed --implys-- potentially call destructor)In other words, at construction the compiler keeps record of what member object was fully constructed then in case of exceptions it calls the destructor accordingly.You see Arvind? We are already paying the price of keeping record of constructed member objects.If it was not the case, and in case the compiler really calls the destructor of the object at construction (which doesn't make sense) then i will see the following printed:called deletebar::dtorfoo::dtor-----called deletebar::dtorfoo::dtorfoo::dtorWhich means 1- i freed a pointer which was not allocate yet "TheFree d", 2- then destroyed and object which was not constructed yet "bar b", 3- then destroy a successfully constructed object "foo f" which is ok.The mental model you are all trying to convince me about is not right.There is no systematic destruction in reverse order of member objects at construction. "At construction", this is my key point.Given the proof above.Since the compiler already keeps track of the member objects already successfully constructed, isn't more logic to allow shuffling the order of member object initialization?I would like see my commented out line of code above, work and produce correct code despite not respecting the order of initialization. I believe that this flexibility won't change the actual rule of destroying member objects in reverse order.Because if my object at construction is successfully constructed, if I impose on its destructor to destroy its members in reverse order, that rule doesn't affect me. No matter what was the order of initialization of its member objects. It is already constructed safe and sound.PS: this doesn't hold for class subobjects (i.e base classes)I hope this time it was clear.It is very difficult to explain by text,  i really will stop here.N
-------- Original message --------From: connor horman via Std-Proposals <std-proposals_at_[hidden]> Date: 2/26/22 12:22 AM (GMT+04:00) To: std-proposals_at_[hidden] Cc: connor horman <chorman64_at_[hidden]> Subject: Re: [std-proposals] Relax condition for potentially invoked destructor in constructor On Fri, 25 Feb 2022 at 14:45, organicoman via Std-Proposals <std-proposals_at_[hidden]> wrote:Ok, it looks like the thread is still on.Call me wrong or everyday Bob or whatever. But here we go.For me C++ should be intuitive to understand.Armed with some logic and sense is enough to understand its construct. No need to run to the standard everytime to understand simple things unless there is a risk of legacy heritage from C.In our case, Compilers implementers allowed rearranging the members initialization for a reason, i hope it is logic, right?So if i have:struct base {};struct derived: public base{};struct mostDerived: public derived{ int a; double d; mostDerived(): d(3.14), a(123) {}};To get "mostDerived" constructed then i need "derived" constructed. In turn, to get "derived" constructed i need "base" constructed.So obviously there is an ordering here.It does make sense to abide to it.My question now is: -why should i impose this ordering on the data members "a" and "d" in the "mostDerived" class?-Isn't the same if I constructed "d" then "a" or the other way around?1) The initialization order is defined such that it is consistent between constructors so that the destructor can always ensure that it destroys objects in reverse order of construction, regardless of the definition of those constructors2) The initializer of d may refer to a, including in ways that are invalid for indeterminate values3) The destructor of d may refer to a, including in ways that are invalid for on object for which the lifetime has ended (tied back to destruction order, which, as stated multiple times in this thread, is always inverse of initialization order)In that program, the compiler may 100% reorder or interleave the initializers under the as-if rule, because neither contains any observable behaviour, neither contain side effects to the same scalar object, and neither depends on the other. However, if you can observe the order, that order is defined in the standard so you can rely on that order.Don't tell me the standard says. I want one logic reason or an example. Also, if my compiler allows me to shuffle the initialization order of data members. For me, that is a strong guarantee that it will catch the following case and process it without error:Given the struct: base and derived above,struct mostDerived: public derived{ int a; double d; mostDerived (): d(3.14), derived(), a(123){}};So logically, the compiler should help me here and make sure that this constructor produce correct code, right?Otherwise i need always to compile my constructor myself before my compiler does it. Which is not programming for me.Same deal here. It doesn't actually affect observable behaviour to reorder these initializers, so the compiler could 100% do what it wants. The issue is when it becomes observable.I'm also unsure what you mean by "produce correct code". There's no source of undefined behaviour that would make this code incorrect, and, as I observed above, any order would do the exact same thing. So my proposal, or suggestion, is relaxing this ordering rule on data member initialization. But if this flexibility is not desired, and ppl are content with what they have, then it's ok.I will stop here.NadirSent from my Galaxy-------- Original message --------From: Jason McKesson via Std-Proposals <std-proposals_at_[hidden]> Date: 2/25/22 10:46 PM (GMT+04:00) To: std-proposals_at_[hidden] Cc: Jason McKesson <jmckesson_at_[hidden]> Subject: Re: [std-proposals] Relax condition for potentially invoked
  destructor in constructor On Fri, Feb 25, 2022 at 12:30 PM organicoman via Std-Proposals<std-proposals_at_[hidden]> wrote:>>> Keep in mind that objects are guaranteed to be destroyed in the opposite order of construction. If you have multiple constructors that may construct your subobjects in different orders, you must also allocate run-time memory to record the order of initialization in order for your destructor to destroy the objects in the correct order. That's an unacceptable cost to a lot of people.>> What you are talking about here, and i guess most of the participant in this discussion, is the base classes of a derived class (known as subobjects).> For that case. Yes, the order of destruction MUST follow the order of construction in reverse, and that is guaranteed by the standard.>> In our case we are talking about " MEMBER Objects", objects declared inside the body of the structure.Members are a *kind* of subobject.> The compiler is free to rearrange their layout however it sees fit for a compact memory and to avoid extra instruction to fetch a member if it is not at the right memory boundary.The compiler is permitted to insert padding, but it's permitted to dothat for *any* subobject, base or member. What the compiler is *not*allowed to do is rearrange the *order* of members entirely. There arecases where the compiler can, but since C++11, that is restrictedspecifically to cases where some members are of different accessclasses, and this reordering can only happen *between* members ofdifferent access classes. Within an access classes, later members mustcome after earlier ones.This is explicitly codified in C++20's pointer comparison rules:> If two pointers point to different non-static data members of the same object, or to subobjects of suchmembers, recursively, the pointer to the later declared member isrequired to compare greater providedthe two members have the same access control (11.9), neither member isa subobject of zero size, andtheir class is not a union.So your conclusion arises from faulty premises. The layout of a structmust be such where later declared members are later in the object thanearlier ones, within the same access control. Ordering is not subjectto the compiler's whims.-- Std-Proposals mailing listStd-Proposals_at_[hidden]://lists.isocpp.org/mailman/listinfo.cgi/std-proposals--
Std-Proposals mailing list
Std-Proposals_at_[hidden]
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2022-02-25 22:36:45