Discussion Thread: DIP 1040--Copying, Moving, and Forwarding--Community Review Round 1
Timon Gehr
timon.gehr at gmx.ch
Wed Mar 17 01:06:45 UTC 2021
On 16.03.21 23:30, deadalnix wrote:
> On Tuesday, 16 March 2021 at 09:14:58 UTC, Walter Bright wrote:
>> Postblit's problems arose from it not having access to both objects.
>> The opAssign does have access to both, and the qualifiers can be
>> applied to both parameters, so I don't see a barrier to it working.
>>
>
> YES!
>
> This is why it is unsuitable for copies. During a copy, there are 2
> objects.
>
> Any solution, that provide to objects when moving, a situation where
> only one object exists, will just open a can of worm of the same nature
> as postblit for copies opened.
>
> This is self evident. This is so obvious that I don't know how to unpack
> it any further.
>
> Pretend you have one object when you have two => problems.
> Pretend you have two objects when you have one => problems.
>
>> An opAssign gives the implementer complete control over the operation
>> of it, including when and how destruction takes place of the original
>> destination's contents.
>
> That break all the invariant provided by the ctor/dtor mechanism and
> because struct are composable (you can use structs as member of structs)
> then the mess is not bounded to the one struct you are toying with.
This reasoning makes sense to me, but perhaps sometimes you don't want
to actually destroy the move target. E.g., it's possible that you want
to move a dynamic array element-wise instead of by reference to avoid
creating dangling references (if you had pointers to the array
elements). There's no good reason why that should be possible for static
array members but not dynamic array members. This is what Walter means
by complete control. So a "better" solution would allow for this while
still doing the right thing for new fields by default.
For the common case, I think the move opAssign should be auto-generated
from the destructor and the move constructor/postblit. So whatever the
solution is, having a move constructor/postblit should not require an
implementation of the move opAssign, but it should rather auto-generate
it using the move constructor/postblit.
So I think the reasoning may not fully apply to move opAssign. OTOH, I
think you are making a very strong point that a (move) postblit is in
the common case better than a move _constructor_. Postblit for copies is
bad, but for moves into destructed/uninitialized memory, it is often
precisely what we want. I guess the main benefit of a move constructor
is that it allows you to fix internal references, while you are out of
luck with postblit.
Summary:
move constructor:
move into uninitialized memory, error prone, internal references supported
(move) postblit:
move into uninitialized memory, composes well, internal references not
supported
move opAssign: move into existing memory, error prone, allows for full
control but can usually be auto-generated from destructor and move
constructor/postblit
So, obvious question: What design satisfies the following constraints?
move constructor v2:
move into uninitialized memory, composes well, internal references supported
move opAssign v2:
move into existing memory, composes well, allows for full control, but
can be auto-generated in the common case
It's pretty clear what a move constructor v2 would be: Require all
fields to be explicitly initialized, even those that have a field
initializer. This would allow to a manual postblit-like implementation,
i.e., first this.tupleof=move(other.tupleof), then do fixup, as well as
repairing internal references if that's required.
Issues with this:
- It does not really make sense to reinitialize fields of immutable
objects that have a field initializer.
- You can't truly get postblit behavior as IIRC std.algorithm.move
actually writes the init value over its argument, so maybe still
supporting postblit is better. (Unless the optimizer is reliably good
enough here.)
What's a move opAssign v2? Maybe move opAssign but require all fields to
be mentioned?
One issue with original DIP that occurred to me while writing this stuff
down:
- How do you manually move an object field-wise in a move constructor or
move opAssign? You'd want to move out the fields, but the compiler does
not really have a good way to see that. If you use std.algorithm.move,
you get additional overhead that a direct call to the move constructor
or move opAssign would not suffer. Do we rely on the optimizer here?
More information about the Digitalmars-d
mailing list