C++ Logo

std-discussion

Advanced search

Re: Accessing data members in constructors' function try bocks

From: Edward Catmur <ecatmur_at_[hidden]>
Date: Wed, 18 Jan 2023 17:15:02 +0000
On Wed, 18 Jan 2023 at 17:11, Brian Bi via Std-Discussion <
std-discussion_at_[hidden]> wrote:

>
>
> On Wed, Jan 18, 2023 at 11:40 AM Lénárd Szolnoki via Std-Discussion <
> std-discussion_at_[hidden]> wrote:
>
>>
>>
>> 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.
>>
>
> But it goes beyond that. It's apparently UB just to "refer" to them. This
> parallels language in [class.cdtor]/1
> <http://eel.is/c++draft/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).
>

Right; but it is relaxed for objects outside their lifetime before
construction begins or after destruction completes (see above). This does
seem rather silly.

I suspect [except.handle]/10 has a similar rationale, though I've never
> really thought about how function-try-blocks interact with virtual
> inheritance.
>
>
>>
>> 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
>> --
>> Std-Discussion mailing list
>> Std-Discussion_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>>
>
>
> --
> *Brian Bi*
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>

Received on 2023-01-18 17:15:15