C++ Logo

std-proposals

Advanced search

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

From: Sébastien Bini <sebastien.bini_at_[hidden]>
Date: Thu, 18 Aug 2022 14:37:58 +0200
On Thu, Aug 18, 2022 at 12:35 AM Edward Catmur <ecatmur_at_[hidden]>
wrote:

> Ah, I'm talking about swap implemented memberwise, figuratively:
>
> void T::swap(T& rhs) {
> using std::swap;
> swap(a_, rhs.a_);
> swap(b_, rhs.b_);
> swap(c_, rhs.c_);
> // ...
> }
>
> So, after inlining, `y = reloc x;` becomes:
>
> swap(y.a_, x.a_);
> swap(y.b_, x.b_);
> swap(y.c_, x.c_);
> // ...;
> x.~T();
>
> Usually each data member swap would be inlined, and the compiler would
> observe that it just needs to swap two chunks of memory, with exceptions
> for self-referential types.
>
> It's certainly not more efficient than destroy-and-rebuild; and it's more
> work (at present, you have to write out a swap function by hand). But it
> is safe against exceptions.
>

The problem is then, as you pointed out, we don't have anything at the
moment to default generate a memberwise swap implementation. I've seen this
proposal P0198R0 (
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0198r0.pdf), but
I don't know what kind of reception it got.

What about a mixed approach, that could take the best of both worlds? T&
operator=(T src) = reloc; would generate the assignment operator with the
same aliasing as with the reloc ctor. This bit is important, as it means
that the source object will be leaving the assignment operator in a
destructed state (and as such its destructor will not be called, as if by
the reloc ctor).

Then I see two possible implementations:

   1. If T has a noexcept reloc ctor and dtor, then destroy-and-relocate is
   performed.
   2. Otherwise, we perform a memberwise destroy-and-relocate by calling
   the prvalue-assignment operator on each subobject:
      - The prvalue-assignment may be user-provided (in which case a
      temporary copy of the subobject is passed) or generated by =reloc.
      - This generated code shall handle exceptions in a way that is
      similar to constructor initializers: if any subobject prvalue-assignment
      operator throws, then the destructor of each source subobject
that has not
      been relocated will be called before the exception is propagated. This
      should be exception safe, as if done by the copy-and-swap idiom.
      - The only problem I foresee is if T has a user-provided destructor,
      in which case the destructor body will not be called on the source object
      in case an exception is thrown. For that reason, that second
implementation
      should be deleted if T has a user-provided dtor.

Let's illustrate that with an example:
struct T : B
{
    D _d;

    T& operator=(T src) = reloc;
    /* If T has a noexcept reloc ctor and noexcept dtor then equivalent to:
    {
        std::destroy_at(this);
        return *std::relocate_at(&src, this);
    }
    */
    /* Otherwise if T has no user-provided destructor then equivalent to:
    {
        try {
            B::operator=(src); // call B prvalue-assignment operator.
            // The syntax is incorrect as src is an lvalue but we don't
have
            // a syntax for it, especially considering reloc-aliasing
        }
        catch (...)
        {
            std::destroy_at(&src._d); // destruct all source subobjects
that have not been relocated
            throw;
        }
        _d = src._d; // call D prvalue-assignment operator
        return *this;
    }
    */
    /* Otherwise, T& operator=(T) is deleted. */
};

Received on 2022-08-18 12:38:10