C++ Logo

std-discussion

Advanced search

Re: Fwd: Some feedback on scope guards

From: Edward Catmur <ecatmur_at_[hidden]>
Date: Sun, 16 Apr 2023 16:30:11 -0300
On Sun, 16 Apr 2023, 15:36 Andrey Semashev via Std-Discussion, <
std-discussion_at_[hidden]> wrote:

> On 4/16/23 19:38, Jason McKesson via Std-Discussion wrote:
> > On Sun, Apr 16, 2023 at 11:36 AM Andrey Semashev
> > <andrey.semashev_at_[hidden]> wrote:
> >>
> >> On April 16, 2023 5:29:27 PM Jason McKesson via Std-Discussion
> >> <std-discussion_at_[hidden]> wrote:
> >>
> >>>
> >>> It'd be great if we had some kind of attribute that would make it a
> >>> warning to use such a type in a non-block scoped way, or outside of
> >>> other block-scoped types. But otherwise, the concept is valid. And so
> >>> long as that concept is valid, there is no reason to a priori forbid
> >>> their use as members of other block-scoped types.
> >>
> >> No, the concept doesn't look valid to me.
> >
> > I think we may have mixed up the "concept" in question. The attribute
> > thing was just an off-the-cuff suggestion. "The concept" was the
> > validity of having block-scoped types being a member of another object
> > which itself is block-scoped.
> >
> >> What about having a block-scoped
> >> unique_ptr pointing to such type? Presumably, unique_ptr won't be
> marked as
> >> block-scoped, so should this be forbidden?
> >
> > "Forbidden" is a strong word, but should it be discouraged? Yes.
> >
> > Let's break this down. What we have is a type that is, by design,
> > intended to be used in a statically block-scoped way. The type is
> > non-moveable, so users cannot transfer ownership from its originating
> > named object (prvalues allow them to escape a function call, but at
> > some point, it gets a name and cannot be moved from there). This is a
> > conceptual invariant: once they get a name, their destructor is
> > statically bound to the C++ language scope of that name (outside of
> > doing oddball things like directly calling the destructor).
>
> No, scope guards *are* moveable. And I can imagine use cases that rely
> on them being moveable, like returning an epilogue from a function.
>
> auto start_transaction(transaction_data& tr)
> {
> scope_exit completion_guard([&] { tr.complete(); });
> tr.init_with_data_data(x, y, z);
> return completion_guard;
> }
>
> And regarding storage, here's another use case that makes use of a scope
> guard as a non-automatic object - program exit handlers.
>
> // At namespace scope
> scope_exit at_exit_guard([] { puts("Good bye cruel world!"); });
>
> Or make that thread_local to make it a thread exit handler.
>

Those are both scope_exit, though, not scope_success or scope_failure. When
would it make sense to return one of the latter two or to use them at
namespace scope?

Yes, around 97.5% of use cases will be complete objects on the stack
> with no moves and no (de)activates, but that doesn't mean the scope
> guard is inherently a non-moveable always-complete object on the stack.
> No, a scope guard is a class that calls arbitrary function from its
> destructor, period. Nowhere in this definition is there anything about
> its storage or composability, and I don't see why there should be.
>
> This whole idea of limiting scope guards to just complete objects on the
> stack only appeared as a workaround for incompatibility with coroutines,
> and to that I say I don't like this tradeoff, not at all. Again, if you
> want to limit scope guards to just stack-based use (I don't; I find it
> useful to be able to use scope guards in other contexts, like above)
> then just stop using classes and destructors for this purpose. This is
> no longer a tool for describing and creating objects, this is a tool for
> describing control flow, so you should define it accordingly. You will
> sidestep the uncaught_exceptions() issue elegantly, and introduce nice
> features like anonymous scope guards in the process. And no, try/catch
> doesn't cut it as a replacement.
>

Thinking about it some more, the nature of my suggestion is that the
destructor of the success/failure scope guard should be called by the
compiler, since only the compiler knows whether the destructor call was
caused by normal or exceptional scope exit. So it might be OK to have those
scope guards as namespace scope objects, or as subobjects, as long as the
containing objects' destructors are declared as defaulted.

Actually, I would not be opposed to having both the native core language
> scope guards and the scope guard types, for different use cases.
>
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>

Received on 2023-04-16 19:30:26