C++ Logo


Advanced search

Re: Some feedback on scope guards

From: Jason McKesson <jmckesson_at_[hidden]>
Date: Thu, 13 Apr 2023 15:01:31 -0400
On Fri, Apr 7, 2023 at 1:31 PM Andrey Semashev via Std-Discussion
<std-discussion_at_[hidden]> wrote:
> Hi,
> I hope this is the right place to report feedback on C++ Extensions for
> Library Fundamentals TS v3.
> On boost-dev mailing list, there was this discussion that highlighted
> some issues with scope_success/scope_fail scope guards that are defined
> in the Library Fundamentals TS:
> https://lists.boost.org/Archives/boost/2023/04/254418.php
> https://lists.boost.org/Archives/boost/2023/04/254420.php
> (Sorry, the thread got split for some reason.)
> In short, Andrzej Krzemienski points out that using
> unhandled_exceptions() as a way of detecting whether an exception has
> been thrown doesn't work with coroutines because after creating the
> scope guard and capturing the result of unhandled_exceptions() the
> coroutine may be suspended and resumed in a different context where the
> number of unhandled exceptions is different. He then goes on to
> demonstrate the problem with Boost.Scope, an implementation of the scope
> guards proposed for review in Boost.

I think this discussion has kind of gotten a bit confused, so I'd like
to try to refocus it on the problem.

The types `scope_success` and `scope_fail` exist to solve a particular
problem: they want to know if a scope is being exited via an exception
thrown from within that scope.

The traditional method of using a destructor for a stack object is
ineffective since it doesn't know why it's being destroyed. Using
`unhandled_exceptions` is a correction for this. The count is queried
during the constructor and preserved until destruction. If an
exception is fired from within the scope, then the number of unhandled
exceptions will be larger. This is considered a "fail" condition.

However, this test is based on the assumption that the current thread
stack is maintained from when the constructor was called to when the
destructor was called. Therefore, with coroutines being able to
abscond with part of the stack and move it elsewhere, the assumption
is broken and the tool no longer works.

So given all of that, here's an odd question: Why are we doing this at all?

Think about it: the original problem is to detect whether an exception
is being fired from within some bound scope and execute some code
based on that. But we already have a tool for that: `try/catch`.

Oh sure, it requires creating an explicit scope with curly braces. And
it puts the fail-handling code below the block that might throw rather
than near the objects that it will act on. But ultimately, it solves
the problem.

And for the success state... just put the code below there; if an
exception is emitted, it will never be executed.

So overall, the main thing we get out of `scope_success/fail` is
changing the locality of the fail/success code.

Considering how difficult it is to answer the basic question in
coroutines without an explicit `try` block... maybe we should just not
support these things? Maybe we should just encourage people to use
`try/catch` blocks if they need to check for scope failure. Yes, it's
not as visually pleasing, but it solves the problem.

Received on 2023-04-13 19:01:45