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