C++ Logo


Advanced search

Re: Some feedback on scope guards

From: Marcin Jaczewski <marcinjaczewski86_at_[hidden]>
Date: Mon, 17 Apr 2023 15:17:42 +0200
pon., 17 kwi 2023 o 14:54 Andrey Semashev via Std-Discussion
<std-discussion_at_[hidden]> napisał(a):
> On 4/17/23 15:34, Marcin Jaczewski wrote:
> > pon., 17 kwi 2023 o 14:11 Andrey Semashev via Std-Discussion
> > <std-discussion_at_[hidden]> napisał(a):
> >>
> >> On 4/17/23 14:49, Marcin Jaczewski wrote:
> >>> pon., 17 kwi 2023 o 13:44 Andrey Semashev via Std-Discussion
> >>> <std-discussion_at_[hidden]> napisał(a):
> >>>>
> >>>> On 4/17/23 14:36, Andrey Semashev wrote:
> >>>>> On 4/17/23 14:11, Marcin Jaczewski via Std-Discussion wrote:
> > (...)
> >>>>>
> >>>>> This means every smart pointer and container and any other user-defined
> >>>>> type that uses pointers as members would have to be updated for this to
> >>>>> work. This is a big ask.
> >>>>
> >>>> Also, I wonder how things like shared_ptr and unique_ptr<T[]> would work
> >>>> with this.
> >>>>
> >>>
> >>> Do even langage need to define behavior for it?
> >>
> >> You're defining a new operator (or even a set of operators), so yes, as
> >> a matter of fact the core language and the standard library has to
> >> define the behavior of these operators in each and every case, scope
> >> guards or not. Does the language guarantee that `T::operator unwind`
> >> will be called by unique_ptr<T[]> for every member of the array? How
> >> would that be implemented? What about shared_ptr<T>? What if there are
> >> two shared_ptrs pointing to the same object and only one of them is
> >> destroyed due to an exception?
> >>
> >
> > Operators are used only for stack variables, nothing else.
> Why? This would mean namespace-scope scope guards won't work.

Yup, this should work here too, as this is statically determined when
destroyed and why.

> > You can call them directly as any other operator or function.
> > But it will not propagate through pointers, you will need to manually
> > propagate it.
> > Same like copy work.
> >
> > Std can define on its own discretion whether some smart pointer
> > propagates it or not.
> > `unique_ptr` could be considered, `shared_ptr` should not as this will
> > be a coin toss what behavior
> > you get ("was this last shared pointer or only first of many?")
> This distinction is unexpected.
> unique_ptr p1(new scope_success([]{})); // now the scope guard works
> shared_ptr p2(std::move(p1)); // now it doesn't

And `foo(p2)` will make it not work any more too. Or
shared_ptr p2(std::move(p1));
catch (...)
By default is only asking for problems, it should be explicit when and
why this should propagate.

> >> How does this new operator interact with virtual functions and base
> >> classes? Do you allow the operator to be =deleted? If so, what does it
> >> mean? Is the object now indestructible? (Because whether there will be
> >> an exception or not is a runtime property, not compile-time.)
> >
> > This is not a problem as this operator only is applied to stack variables.
> > This is determined at compile time what type it is.
> What is not a problem?
> You can have a unique_ptr on the stack that points to a base class with
> a virtual destructor. Will the unique_ptr call `operator unwind` on the
> pointed object? Will this require modification of the pointed type and
> the final derived type of the pointed object?

This is how implementation will implement this smart pointer.
Probably you will need some cooperation with class hierarchy in this case.

> In relation to this, do these expressions call the new operator?

`operator new`? :D

> * `delete p`, where p is a raw pointer
> * `p->~T()` or `r.~T()`, where p is a raw pointer and r is a reference
> This is important for types like optional and variant.

No, the operator is not linked to the destructor, it's simply called
by the compiler before
destruction of the stack object (or global ones).
As your type has a custom copy operator, then it should have a custom
unwind operator.
As this is a new operator all std containers could simply add new
without breaking ABI.
Type will simply propagate it to nested objects (same as it need handle copy).

> > `=deleted` is good question, and I think it should be supported.
> > probably if you "ban" exceptions unwind, class could only be placed in
> > `noexcept`
> > functions, if you ban normal unwind then it can't be a local variable.
> You can have exceptions within noexcept functions. You just can't
> propagate them to the caller.
> The next question would be, should a type be considered trivially
> destructible, if it has a non-defaulted `operator unwind`?
> >> There are likely a lot of other questions such a proposal needs to answer.
> >>
> >>> How do you want scope guard for variables that are not linked in any
> >>> way to scope? Is even one correct answer for this?
> >>
> >> There is an answer in the current TS - the scope guard calls its action
> >> when its destructor is called. scope_success/scope_fail also add the
> >> logic around uncaught_exceptions(), which, although is not working as
> >> desired with coroutines, is still rather simple and straightforward.
> >
> > I do not think this is "simple", see `shared_ptr` when its lifetime is
> > undetermined
> > locally and destructor `uncaught_exceptions` unrelated to the value
> > from the point of creation.
> It is simple, as the behavior follows the basic rules of object lifetime
> that existed since the beginning of C++.

But not the lifetime of heap objects that can "outlive" the program itself.
I can have a `unique_ptr` with `noop` destructors. Or some handler.
Sometimes I would like to propagate "unwind" but sometimes I do not.
Simply this should not be a role for smart pointers to determine this.

> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion

Received on 2023-04-17 13:17:55