C++ Logo


Advanced search

Re: Fwd: Some feedback on scope guards

From: Andrey Semashev <andrey.semashev_at_[hidden]>
Date: Sun, 16 Apr 2023 21:36:26 +0300
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.

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.

Actually, I would not be opposed to having both the native core language
scope guards and the scope guard types, for different use cases.

Received on 2023-04-16 18:36:41