C++ Logo


Advanced search

Re: Some feedback on scope guards

From: Andrey Semashev <andrey.semashev_at_[hidden]>
Date: Mon, 17 Apr 2023 15:53:54 +0300
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.

> 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

>> 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?

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

 * `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.

> `=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++.

Received on 2023-04-17 12:53:58