Discussion: Rvalue refs and a Move construtor for D

Suleyman sahmi.soulaimane at gmail.com
Thu Sep 5 15:32:31 UTC 2019


On Thursday, 5 September 2019 at 12:09:33 UTC, Eduard Staniloiu 
wrote:
> On Wednesday, 4 September 2019 at 21:59:46 UTC, kinke wrote:
>> On Wednesday, 4 September 2019 at 21:09:27 UTC, Exil wrote:
>>> So you change the ABI to pass by a pointer to the object on 
>>> the stack. In cases where we use some "move" intrinsic, a 
>>> pointer to a lvalue (passed in to the move()) is passed in 
>>> place of the pointer to the object on the stack?
>>
>> Yes.
>>
>>> Because of this, even though the function doesn't use a 
>>> reference. It could unknowingly change state outside of the 
>>> scope of the function. That'll entirely depend on the use if 
>>> they mistakenly use `move()` where they didn't mean to.
>>
>> Yes, the assumption being that people do use `move` when they 
>> mean to (it's explicit after all) and are fine with not being 
>> able to make any assumptions about its state after the move. 
>> Re-assigning would still be possible.
>>
>> But as stated further up, the crux is probably the lifetime 
>> for moved lvalues, i.e., the by-value parameter not being 
>> destroyed right before/after returning, but when the 
>> moved-from lvalue goes out of scope.
>> Maybe we could destruct the moved-from lvalue after the call 
>> and reset it to `T.init`. It would be destroyed a 2nd time 
>> when going out of scope.
>
> So you are saying that Exil's example
> ```
> void main()
>     {
>         Foo lvalue;
>
>         lvalue.value = 0;
>         bar(move(lvalue)); // intrinsic move
>
>         assert(lvalue.value == 10); // passes
>     }
> ```
> would be valid and it's ok?
>
> I'm sorry, but, in my humble opinion, this would be horrible.
> I would expect the contents of the moved lvalue to be reset to 
> `Foo.init`.
> I think the behavior shown above would be error prone and a big 
> source of bugs.
>
> Edi

It looks like correct behavior to me. An lvalue remains usable 
after move. The state may change depending on what the move 
constructor does. But move doesn't end its lifetime, destruction 
happens only at the end of the scope when the lifetime of the 
variable actually ends.

Practical example:
```
struct S
{
     void* bigMem;
     void* end;

     @disable this();

     this(size_t size)
     {
         import core.stdc.stdlib : malloc;

         bigMem = malloc(size);
         end = bigMem + size;
     }

     invariant() { assert((bigMem is null) == (end is null)); }

     this(ref S rhs) @move
     {
         // steal resources from rhs
         bigMem = rhs.bigMem;
         end = bigMem + rhs.length;
         rhs.bigMem = null;
         rhs.end = null;
     }

     @property size_t length() { return bigMem is null ? 0 : end - 
bigMem; }
}

enum _1GB = 1024^^3;

void bar(S s) { assert(s.length == _1GB); }

void main()
{
     auto s = S(_1GB);
     bar(__move(s));  // calls move ctor
     assert(s.length == 0); // s is still usable
}
```



More information about the Digitalmars-d mailing list