Discussion Thread: DIP 1040--Copying, Moving, and Forwarding--Community Review Round 1

tsbockman thomas.bockman at gmail.com
Thu Mar 18 20:58:45 UTC 2021


On Thursday, 18 March 2021 at 10:01:12 UTC, Walter Bright wrote:
> On 3/16/2021 11:11 AM, tsbockman wrote:
>> You just have different results in mind from what your wrote 
>> in the DIP.
>
> The desired result is clear - two objects exist before the move 
> assignment, and one afterwards. If both objects are the same 
> instance, then it should be a no-op.
>
> The wording could be improved - want to make a stab at it?

Sure.

Old wording:
> A Move Assignment Operator is a struct member assignment 
> operator
> that moves, rather than copies, the argument corresponding to 
> its
> first parameter into the constructed object. After the move is
> complete, the destructor is called on the original contents of
> the constructed object. The argument is invalid after this move,
> and is not destructed.

Proposed new wording:
> A Move Assignment Operator is a struct member assignment 
> operator
> that moves, rather than copies, the source argument into the
> destination argument. The source argument corresponds to the 
> first
> parameter, and the destination argument corresponds to the 
> implicit
> `this` parameter.
>
> If both arguments refer to the same object, the Move Assignment
> Operator shall have no effect, and that object remains valid.
>
> Otherwise, the effect shall be to destroy the old contents of 
> the
> destination and to move the contents of the source into the
> destination. The source is invalid after the Move Assignment
> Operator returns, and is not destroyed.
>
> The Move Assignment Operator implementation may perform the
> destruction and move in any order, however it must ensure that
> the source is not destroyed when the old contents of the
> destination are destroyed, even if the source is indirectly 
> owned
> by the old contents of the destination.

The final paragraph is the tricky part: I thought about the 
"destroy after" thing some more, and realized that while it is 
neither necessary nor helpful when the source and destination 
refer to the exact same object, it *is* helpful when the old 
destination may *indirectly* own the source.

For example, what if there is a singly-linked list where each 
`next` link is a unique smart pointer? When removing the current 
`head` of the list, we may wish to move `head.next` into `head`. 
If `head` is destroyed first, it will wrongly destroy the rest of 
the list before the move is complete, including `head.next`.

However, requiring that the destruction of the old destination be 
performed after the move of source to destination doesn't fix 
this problem by itself; an additional step is required: set 
`source` to `null` as part of the move.

So, requiring a specific order is undesirable, because it doesn't 
fix the problem without also requiring the existence of a `null` 
state for every movable type. (My earlier code example uses a 
`null` state.)

Also, requiring a specific order is undesirable because it is not 
the only possible solution to the indirect ownership problem; for 
example, the move assignment operator could scan the old 
destination's indirections and just skip destroying any that 
would invalidate the source.

Thus, I propose we formally leave the details up to each move 
assignment operator's implementer, and limit the language spec to 
forbidding the problem itself, rather than require semantic 
equivalence to a specific solution. Types without problematic 
indirections can destroy before, types with problematic 
indirections and a `null` state can destroy after, and those rare 
types that don't fit into either category can do something more 
creative.


More information about the Digitalmars-d mailing list