C++ Logo


Advanced search

Re: Some feedback on scope guards

From: Andrey Semashev <andrey.semashev_at_[hidden]>
Date: Mon, 17 Apr 2023 15:10:51 +0300
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:
>>>> pon., 17 kwi 2023 o 12:54 Andrey Semashev via Std-Discussion
>>>> <std-discussion_at_[hidden]> napisał(a):
>>>>> On 4/17/23 13:47, Marcin Jaczewski wrote:
>>>>>> pon., 17 kwi 2023 o 12:35 Andrey Semashev via Std-Discussion
>>>>>> <std-discussion_at_[hidden]> napisał(a):
>>>>>>> On 4/17/23 13:27, Marcin Jaczewski via Std-Discussion wrote:
>>>>>>>> Why are multiple destructors bad? Especially if they were handled like
>>>>>>>> `const` overloads.
>>>>>>>> Besides, if a new destructor is no-go, why not add a new operator that
>>>>>>>> simply is called before the destructor?
>>>>>>>> like `operator unwind()` that is called before a proper destructor is
>>>>>>>> used in case of unwind.
>>>>>>> It doesn't matter how you name it. If it doesn't work through other
>>>>>>> types (e.g. if the scope guard is a member or accessed through a
>>>>>>> pointer) then we have a problem with composability. If it does work,
>>>>>>> then we're back to the status quo, since the operator will be called
>>>>>>> under the same conditions as the destructor, and therefore it will need
>>>>>>> to check for uncaught_exceptions().
>>>>>> `operator unwind() = default;`?
>>>>> And what would this mean? That it forwards or that it doesn't?
>>>>> Regardless of how you answer, my previous reply applies.
>>>> Apply same operator to all members.
>>>>>> And if it's not a direct member then we have the same problem with
>>>>>> composible as "copy",
>>>>>> that I suppose is solved problem?
>>>>> I'm not sure what you mean here. Default copy constructors/assignments
>>>>> do forward, but there's no problem with that, so I don't see the relation.
>>>> As the same logic applies in both cases, `T(const T&) = default` apply
>>>> copy for each member.
>>>> `operator throw_unwind() = default;` is equal to:
>>>> ```
>>>> x.operator throw_unwind();
>>>> y.operator throw_unwind();
>>>> z.operator throw_unwind();
>>>> ```
>>>> Of course if `x` is a pointer then this operator is noop. But we can
>>>> easy work around this by composition like:
>>>> ```
>>>> struct mixing_unwind_deref
>>>> {
>>>> template<typename T>
>>>> operator throw_unwind(this T& self) { if (self) (*self).operator
>>>> throw_unwind(); }
>>>> };
>>>> struct MyPointer : std::unique_ptr<scope_gaurd>, mixing_unwind_deref
>>>> {
>>>> };
>>>> ```
>>> 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?

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.)

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.

Received on 2023-04-17 12:11:15