On 18 January 2023 16:02:41 GMT, "Frank B. Brokken via Std-Discussion" <std-discussion@lists.isocpp.org> wrote:
>Hi all,
>
>Recently I noticed item #10 in section 14.4 (Handling an exception), n4917.pdf
>(https://github.com/cplusplus/draft/releases/latest):
>
> 10 Referring to any non-static member or base class of an object in the
> handler for a function-try-block of a constructor or destructor for
> that object results in undefined behavior.
>
>Unfortunately, that item isn't elaborated, and somehow I find it
>puzzling. Cleary, if a class has composed data members then the destructors of
>those already constructed data members have been called when the execution
>reaches the constructor's function-try-block handler.
>
>But why would referring to members of basic data types like 'int' be
>undefined? To make matters concrete, consider the following little program:
>
> 1: #include <iostream>
> 2:
> 3: class Int
> 4: {
> 5: int d_value;
> 6:
> 7: public:
> 8: Int(int value);
> 9: };
> 10:
> 11: Int::Int(int value)
> 12: try
> 13: :
> 14: d_value(value)
> 15: {
> 16: throw 0;
> 17: }
> 18: catch (...)
> 19: {
> 20: std::cerr << "value = " << d_value << '\n';
> 21: }
> 22:
> 23: int main()
> 24: try
> 25: {
> 26: Int ival{ 5 };
> 27: }
> 28: catch(...)
> 29: {
> 30: std::cerr << "done\n";
> 31: }
>
>The class is defined in lines 3 thru 9. It has merely an int data member. The
>constructor (lines 11 thru 21) initializes its data member and then throws an
>exception, caught by its handler, where d_value's value is
>displayed. Eventually main's exception handler issues a smalll message.
>
>What puzzles me is why the Int-constructor's handler's use of the object's
>d_value data member would result in undefined behavior. To my understanding,
>since function main's Int object is a local variable, it lives in main's stack
>frame. So it ceases to exist once that stack frame is removed from the
>stack. But that hasn't yet happened when Int's constructor throws, so unless
>something magic's happening the Int object still exists by the time the
>execution reaches the constructor's function-try-block handler.
>
>I looked for answers to my puzzle in the
>https://lists.isocpp.org/std-discussion/ archives, looking for things like
>'exceptions thrown by constructors' but I didn't find a posting covering my
>puzzle. Maybe I'm overlooking something trivial? In any case I'd appreciate
>receiving some background info about section 14.4's item #10.
>
>Thanks in advance,
>
It's probably more of an object lifetime thing. Within the catch block all of the non-static data members' lifetime has ended or never started. Whether the data member is trivial or not, is not relevant. Accessing it outside of its lifetime is UB.
But it goes beyond that. It's apparently UB just to "refer" to them. This parallels language in
[class.cdtor]/1.
I think [class.cdtor]/1 has to do with virtual base classes: before the constructor starts or after the destructor finishes, the code that "finds" virtual bases (and, therefore, members of virtual bases) might not function properly. Unfortunately the record of discussion back in 1994 and 1995 is pretty incomplete.
In principle the restriction could probably be relaxed for members and bases if the "path" to them doesn't go through any virtual inheritance. Why didn't they do that? Probably because no one really thought it was necessary to allow this other than for (what were then called) POD types (i.e. for C compatibility).