Date: Wed, 18 Jan 2023 16:40:33 +0000
On 18 January 2023 16:02:41 GMT, "Frank B. Brokken via Std-Discussion" <std-discussion_at_[hidden]> 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.
Maybe you can carve out an edge case for "trivial" data members (for some aspect of "trivial"), but the language seem to go in a different direction. For example AFAIK calling the pseudo-destructor on scalar object went from noop to ending the object's lifetime, making a subsequent access UB.
Cheers,
Lénárd
>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.
Maybe you can carve out an edge case for "trivial" data members (for some aspect of "trivial"), but the language seem to go in a different direction. For example AFAIK calling the pseudo-destructor on scalar object went from noop to ending the object's lifetime, making a subsequent access UB.
Cheers,
Lénárd
Received on 2023-01-18 16:40:39