C++ Logo

std-discussion

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));
try
{
    bar(p2);
}
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