C++ Logo

std-proposals

Advanced search

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

From: Sébastien Bini <sebastien.bini_at_[hidden]>
Date: Tue, 31 May 2022 10:51:34 +0200
Hi all,

> The defaulted operator reloc can't work here anyway, since Transaction
needs to be informed that DB has relocated.
>
> So Transaction must already have a ctor Transaction(Transaction&&, DB&)
and the relocator of T must invoke that ctor:
>
> T::operator reloc(T&& src) : _trans(std::move(src._trans), _db) {}

Indeed, my bad. I do have a gut feeling that synthesized relocation may
lead to problems, mainly because it may change the destruction order of
subobjects. However I cannot find a convincing example where the default
move constructor + destructor on the whole object does the right thing, and
where the default operator reloc() mixing true relocations and synthesized
relocations on subobjects does not :/ I guess we'll keep synthesized
relocation for the moment.

> template<class T> requires is_noexcept_relocatable_v<T>
> void swap(T& lhs, T& rhs) {
> T temp = relocate(&lhs);
> relocate_at(&rhs, &lhs);
> new (&rhs) T(reloc temp);
> }

Actually I am not sure there are any real benefits of having `T
std::relocate(T*)` if we have the reloc operator (which is a safer version
of std::relocate) and std::relocate_at. swap can be written without
std::relocate:

template<class T> requires is_noexcept_relocatable_v<T>
void swap(T& lhs, T& rhs)
{
    std::aligned_storage_t<sizeof(T), alignof(T)> tmp;
    std::relocate_at(&lhs, reinterpret_cast<T*>(&tmp));
    std::relocate_at(&rhs, &lhs);
    std::relocate_at(reinterpret_cast<T*>(&tmp), &rhs);
}

BTW 'new (&rhs) T(reloc temp);' can be replaced by placement-reloc: 'reloc
(&rhs) temp;'

> gsl::non_null<int*> p1, p2, p3 = ...;
> gsl::non_null<int*> q1 = reloc p1; // OK in your world
> q1 = reloc p2; // OK??
> std::swap(q1, p3); // OK??

`reloc obj` (with obj of type T) merely returns a temporary object of type
T, built by relocating from obj (just like std::relocate). obj is
considered destructed and as such is not left in a moved-from state. We
rely on mechanisms such as copy-elision or clearly identified patterns
(object initialisation with a reloc statement, like: `auto a = reloc obj;`)
to remove that temporary in most cases.

With `q1 = reloc p2;`, as it is phrased in the paper, reloc p2 will return
a temporary that is assigned into q1. p2 is considered destructed
(relocated into the temporary). Then q1's move assignment operator will be
called.

We could go further and claim that assigning from a reloc statement
transparently calls destructor + placement reloc (std::destroy_at(&q1);
reloc (&q1) p2;). I am not sure that's desirable at all:
- We don't enforce this at the language-level for other assignment
operators.
- Why limit to the operator reloc? Assigning from any pr-value of the same
type could be optimized into destructor + placement reloc, but that may
break existing code?
- How to handle exceptions? This optimization could be turned off if the
destructor or operator reloc are noexcept(false).
- Users can implement their assignment operator with std::swap which takes
advantage of relocation (as it is often the case anyway).

> Similarly, if you "relocated" a `std::mutex`, *bad things would happen*.
You can't have a mutex suddenly change memory addresses and expect the rest
of the program to just be cool with that.

Yes there are objects that are not even relocatable, like std::mutex. Such
types can prohibit relocation by deleting their operator reloc: `operator
reloc(T&&) = delete;` (which should already be implicitly deleted since
their move constructor is also deleted.)

Best regards,
Sébastien

On Tue, May 31, 2022 at 12:15 AM Edward Catmur <ecatmur_at_[hidden]>
wrote:

> On Mon, 30 May 2022 at 12:07, Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
> wrote:
>
>> On Mon, May 30, 2022 at 1:56 PM Edward Catmur <ecatmur_at_[hidden]>
>> wrote:
>>
>>> [...]
>>>
>> template<class T> requires std::is_noexcept_relocatable_v<T>
>>> void swap(T& lhs, T& rhs) {
>>> T temp = relocate_at(&lhs);
>>> new (&lhs) T(relocate_at(&rhs));
>>> new (&rhs) T(relocate_at(&temp));
>>> }
>>>
>>> (relocate_at is the magic library function that (destroys and) relocates
>>> its argument into a returned prvalue.)
>>>
>>
>> Terminology nit: `relocate_at` takes a pointer parameter `dest`, just
>> like `construct_at` and `destroy_at`.
>> *`relocate` with no suffix* is the magic library function that relocates
>> into a returned prvalue.
>> https://quuxplusone.github.io/blog/2022/05/18/std-relocate/
>>
>
> Ah, yeah, thanks. And that last relocate is incorrect; it should use the
> relocation operator keyword, since temp should not be destroyed at the end
> of scope:
>
> template<class T> requires is_noexcept_relocatable_v<T>
> void swap(T& lhs, T& rhs) {
> T temp = relocate(&lhs);
> relocate_at(&rhs, &lhs);
> new (&rhs) T(reloc temp);
> }
>
>

Received on 2022-05-31 08:51:46