C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Relocation in C++

From: Edward Catmur <ecatmur_at_[hidden]>
Date: Fri, 10 Jun 2022 01:04:31 +0100
On Wed, 8 Jun 2022 at 09:34, Sébastien Bini <sebastien.bini_at_[hidden]>
wrote:

> Hi all,
>
> Well, yes; certainly arguing about the exact spelling is bikeshedding. One
>> aspect I think needs further exploration is a similar facility to allow
>> running code before the relocation of subobjects begins, and I think
>> constructor delegation could be the facility to solve that; the other
>> aspect is symmetry with operator=(T).
>>
>
> The delegating constructor will not have the ability to do memberwise
> relocation and to avoid the destructor call. As such using the delegating
> constructor to inject code right before relocation will lose the benefit of
> having a relocation ctor in the first place.
>

Yes, this is a bit unfortunate. Possibly the delegated constructor could
use std::relocate on subobjects and some trick found to prevent the
destructor call. Or we could return to function-try-with-init.

Why would you need to have that option? We don't support that for any other
> ctor (custom code before member initialisation).
>

We do, via constructor delegation. The need here to invoke custom code is
to safely relocate objects that both directly own a resource (i.e. their
destructor releases the resource) and have data members with throwing
relocate. The idea is that before starting to relocate members you can
create a guard object that will release the resource if a data member
relocator throws, to avoid a resource leak.

As for operator=(T), what do you have in mind? I am satisfied with leaving
> it implementation defined, users can freely use destruct + relocate or use
> swap at their own convenience.
>

Ah, hmm. The difficulty is that if we make it support `= default;` and that
does the Obvious Thing of memberwise reloc-assign then we're setting up a
footgun; it would turn into a memory leak for std::unique_ptr; memberwise
reloc-assign is incorrect for resource-owning classes, even if memberwise
reloc-construction is correct for them. But then again, a struct containing
a non_null<unique_ptr> will need to generate a reloc-assignment operator
that calls the reloc-assignment operator of non_null<unique_ptr> (since
that's its only assignment operator), and it would feel weird to have a
default implementation that the user can't access with `= default`.

using N = non_null<unique_ptr<int>>;
struct S { N n; } s1 = ..., s2 = ...;
s1 = reloc s2; // calls into N::operator=(N); N::operator=(N const&) and
N::operator=(N&&) are both deleted.

void sink(T);
>>> void sink(T&&);
>>>
>>> void foo(T val)
>>> {
>>> sink(reloc val);
>>> }
>>>
>>> What happens now if T has opted-out of the ABI break with the
>>> non-standardized attribute? Before the reloc operator was in charge of
>>> building the new instance and could fallback to the move constructor. Now
>>> this is no longer the case as it depends on how the prvalue is consumed.
>>> Isn't it problematic as now the language needs to distinguish between
>>> prvalues whose destructor calls can be alleviated and the others? Which
>>> overload of sink is picked in that case?
>>>
>>
>> The ABI break opt-out doesn't affect overloading, so sink(T) will be
>> called in either case. Supposing T has the attribute applied, the move or
>> copy constructor of T will be called to construct a temporary from val and
>> this temporary is the argument to sink(T); this is much as would happen
>> today, and is exactly the same if T does not have a candidate relocation
>> operator/relocating constructor.
>>
>
> Another way to solve this is to state that `reloc obj` may turn the lvalue
> into a prvalue *or an xvalue* *if obj is a function parameter*. That
> choice would be implementation defined. As such, if obj had opted out of
> the ABI break then `reloc obj` would simply turn obj to an xvalue, no
> temporary would be created and sink(T&&) would be called. Otherwise, in the
> nominal case (obj is okay with the ABI break), then reloc turns obj into
> prvalue and sink(T) is called.
>

The problem is now that you have program structure dependent on an
attribute, which is a no-no; if sink(T) turns out to be broken you won't
find out until you remove the attribute, which goes against the
ignorability principle. So I think the same overload sink(T) needs to be
called in either case.

Received on 2022-06-10 00:04:44