C++ Logo

std-discussion

Advanced search

Re: Accessing data members in constructors' function try bocks

From: Edward Catmur <ecatmur_at_[hidden]>
Date: Fri, 20 Jan 2023 00:46:03 +0000
On Wed, 18 Jan 2023 at 21:18, Frank B. Brokken via Std-Discussion <
std-discussion_at_[hidden]> wrote:

> Dear Edward and Brian,
>
> Thanks for your replies! Still, there are some open ends: see below.
>
> From Edward's last e-mail:
>
> > > But before the exception is thrown its d_value has received a value,
> > > which is then displayed in its function-try-block.
> >
> > Well, yes, but you aren't allowed to access that value, both because that
> > int object is outside its lifetime, and also because you aren't allowed
> to
> > refer to the int object to access its value.
> >
> > .... The Standard has cases where
> > a rule is more general than absolutely necessary for the sake of
> simplicity.
> >
> > > I'm
> > > still struggling with the question why referring to its d_value data
> member
> > > in the constructor's function-try-block implies undefined behavior
> >
> > Your d_value might be a member via a virtual base class of Int. It isn't,
> > but it might be.
> >
> > > But please advise if I should address the question
> > > to 'StackOverflow C++'
> > >
> >
> > No, you're in the right place. You might get an answer on SO, but they do
> > have a tendency to see the Standard as not to be questioned. Here is a
> bit
> > more relaxed.
>
>
> About the open ends: on the one hand you write
>
> (1)
> > you aren't allowed to access that value, both because that
> > int object is outside its lifetime, and also because you aren't allowed
> to
> > refer to the int object to access its value.
>
> and on the other hand:
>
> (2)
> > .... The Standard has cases where
> > a rule is more general than absolutely necessary for the sake of
> simplicity.
>
>
> The 2nd part of (1) looks slightly weird: I can't use that d_value data
> member
> just because I'm not allowed to use it?


You can't access it because you aren't allowed to refer to it. These are
two different terms of art - sorry!

But that returns us to the essence of
> my question: there should be a reason *why* this restriction applies. With
> things like base classes and already destroyed class type data members
> that's
> clear, but for basic type data members not. The 1st part also isn't
> completely
> transparent.


If you mean access, one compelling reason is to allow dynamic safety checks
to detect possible bugs (e.g. the sanitizers, NaT on platforms with such
bit patterns, or in general with "poison" bit patterns).

If you mean referring to such members, there's some sympathy to permitting
referring to data members not of virtual bases during their lifetime -
"address offset calculations" in the words of CWG 597.

So maybe it's just (2)? Maybe the rule is too general 'just to be sure',
> although there may be situations where data members of classes can be
> accessed.
>

Also note that "members" includes member functions as well as data members.
This is relevant when such functions can be virtual.

Section 6.7.3 Lifetime [basic.life] states:
>
> The lifetime of an object of type T begins when:
> (1.1) — storage with the proper alignment and size for type T is
> obtained, and
> (1.2) — its initialization (if any) is complete
>
> which is nice, but other than defining the term 'lifetime' it doesn't
> specify
> what that implies. (1.1) is clear: no storage, no T, but what about (1.2)?
>

That second bullet currently reads "its initialization (if any) is complete
(including vacuous initialization) ([dcl.init]),". Does that help?

Wrt ending lifetimes, 6.7.3. item 1 states:
>
> The lifetime of an object o of type T ends when:
> (1.3) — if T is a non-class type, the object is destroyed,
>
> That's the situation of my d_value data member: a non-class type. But
> d_value
> still exists when the try-block handler is reached as the Int object's
> memory
> is still available at that point.


The object exists, but its memory could have been poisoned in some way,
either in passing or intentionally to help detect use-after-free bugs.

While:
> (1.4) — if T is a class type, the destructor call starts,
> does not apply, since there's no destructor called by the throwing
> constructor. Thus
> (1.5) — the storage which the object occupies is released, or is
> reused by an object that is not nested within o (6.7.2).
> happens when, e.g., the stack frame of the function defining an object
> whose constructor throws ends. But again, that doesn't yet hold true when a
> constructor throws after having initialized some of its basic type data
> members.
>
> Section 6.7.3 also refers to sections 11.9.3 and 11.9.5. In a note it
> states:
>
> 3 [Note 1 : 11.9.3 describes the lifetime of base and member
> subobjects. —
> end note]
>
> then, 11.9.3. states:
>
> 11.9.3 Initializing bases and members [class.base.init]
>
> 1 In the definition of a constructor for a class, initializers for
> ... non-static data members can be specified by a ctor-initializer
>
> which is exactly what happens in my 'class Int' example. In other words,
> d_value has been initialized, and according to 1.4 and 1.5 in 6.7.3. that
> d_value data members remains available when the constructor's
> function-try-block handler is executed.
>
> Section 11.9.5. doesn't quite clarify why d_value can't be used:
>
> 11.9.5 Construction and destruction [class.cdtor]
>
> For an object with a non-trivial constructor, referring to any
> non-static
> member or base class of the object before the constructor begins
> execution
> results in undefined behavior.
>
> But that's not the issue. The constructor has begun its execution. There's
> also its item 5:
>
> 5 A program may end the lifetime of an object of class type without
> invoking the destructor, by reusing or releasing the storage as
> described above.
>
> In the case of my example the destructor isn't invoked, and since d_value
> lives in Int's object which lives in main's stackframe it's there, it has
> been
> initialized and so it remains unclear as to why it can't be used in the
> constructor's function-try-block handler.
>
>
> So thanks again for your replies. Unfortunately, for now I'm still
> wondering
> why d_value in my example cannot be used by the constructor's
> function-try-block handler, except maybe for that item (2) you mentioned:
>
> > .... The Standard has cases where
> > a rule is more general than absolutely necessary for the sake of
> simplicity.
>
>
> Finally:
>
> > Well, that doesn't matter that much; there's lots of UB that isn't
> detected
> > by any compiler. Although it does weigh towards suggesting that it might
> be
> > worth considering relaxing that UB.
>
> I of course 100% agree with your 1st sentence: If I bike around in my area
> I
> see lots of white swans. Even if I see hundreds of them that doesn't of
> course
> mean that there aren't any black ones :-)
>
> Wrt to your 2nd sentence: well, who knows... ;-)
>
> Kind regards,
>
> --
> Frank B. Brokken
> (+31) 6 5353 2509
> PGP Key Fingerprint: DF32 13DE B156 7732 E65E 3B4D 7DB2 A8BE EAE4 D8AA
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>

Received on 2023-01-20 00:46:16