C++ Logo

std-discussion

Advanced search

Re: Accessing data members in constructors' function try bocks

From: Brian Bi <bbi5291_at_[hidden]>
Date: Wed, 18 Jan 2023 12:11:21 -0500
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).

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*

Received on 2023-01-18 17:11:37