C++ Logo

std-discussion

Advanced search

Re: Some feedback on scope guards

From: Andrey Semashev <andrey.semashev_at_[hidden]>
Date: Mon, 17 Apr 2023 14:53:20 +0300
On 4/17/23 14:45, Marcin Jaczewski via Std-Discussion wrote:
> pon., 17 kwi 2023 o 13:36 Andrey Semashev via Std-Discussion
> <std-discussion_at_[hidden]> napisał(a):
>>
>> 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.
>
> I already show how this could be this easy workaround:
> ```
> struct MyPointer : std::unique_ptr<scope_gaurd>, mixing_unwind_deref
> ```
> And another mixing that propagates this operator to `for (auto& p : *this)`.

That's a modification nonetheless. And yes, however minor it may be,
when you have thousands of classes in your project, this is a problem.

Also, I don't think mixing_unwind_deref works the way you're describing.
The type T in the `operator throw_unwind` would be deduced as a possibly
cv-qualified mixing_unwind_deref.

>>>>> You miss an important thing `unwind` will be called only when an
>>>>> exception unwind code.
>>>>
>>>> This doesn't help if you still need to check for uncaught_exceptions().
>>>
>>> But what would be the point for checking this? even more when its have
>>> wrong value
>>> when call in coroutine? `uncaught_exceptions` was needed to check if
>>> it was throw unwind.
>>> This operator gives this info directly. What exactly need I have for
>>> this function?
>>
>> Assuming scope_fail invokes its action from within this `operator
>> unwind`, how would nested scope_fails work?
>
> What exactly nesting you are describing?
> If this `scope_fails` is a member of type then it should be called automatically
> by default implementation of `throw_unwind`. Type can be even plain struct like:
> ```
> struct Foo
> {
> scope_guard x;
> };
> ```
> as all types will have predefined `operator throw_unwind() = default;`

No, I mean this:

  scope_fail sg1([]
  {
    scope_fail sg2([]
    {
      puts("Still a failure?");
    });
  });

  throw 1;

Received on 2023-04-17 11:53:44