Search code examples
d

Does D have a move constructor?


I am referencing this SO answer Does D have something akin to C++0x's move semantics?

Next, you can override C++'s constructor(constructor &&that) by defining this(Struct that). Likewise, you can override the assign with opAssign(Struct that). In both cases, you need to make sure that you destroy the values of that.

He gives an example like this:

// Move operations
this(UniquePtr!T that) {
    this.ptr = that.ptr;
    that.ptr = null;
}

Will the variable that always get moved? Or could it happen that the variable that could get copied in some situations?

It would be unfortunate if I would only null the ptr on a temporary copy.


Solution

  • Well, you can also take a look at this SO question:

    Questions about postblit and move semantics

    The way that a struct is copied in D is that its memory is blitted, and then if it has a postblit constructor, its postblit constructor is called. And if the compiler determines that a copy isn't actually necessary, then it will just not call the postblit constructor and will not call the destructor on the original object. So, it will have moved the object rather than copy it.

    In particular, according to TDPL (p.251), the language guarantees that

    • All anonymous rvalues are moved, not copied. A call to this(this) is never inserted when the source is an anonymous rvalue (i.e., a temporary as featured in the function hun above).
    • All named temporaries that are stack-allocated inside a function and then returned elide a call to this(this).
    • There is no guarantee that other potential elisions are observed.

    So, in other cases, the compiler may or may not elide copies, depending on the current compiler implementation and optimization level (e.g. if you pass an lvalue to a function that takes it by value, and that variable is never referenced again after the function call).

    So, if you have

    void foo(Bar bar)
    {}
    

    then whether the argument to foo gets moved or not depends on whether it was an rvalue or an lvalue. If it's an rvalue, it will be moved, whereas if it's an lvalue, it probably won't be (but might depending on the calling code and the compiler).

    So, if you have

    void foo(UniquePtr!T ptr)
    {}
    

    ptr will be moved if foo was passed an rvalue and may or may not be moved it it's passed an lvalue (though generally not). So, what happens with the internals of UniquePtr depends on how you implemented it. If UniquePtr disabled the postblit constructor so that it can't be copied, then passing an rvalue will move the argument, and passing an lvalue will result in a compilation error (since the rvalue is guaranteed to be moved, whereas the lvalue is not).

    Now, what you have is

    this(UniquePtr!T that)
    {
        this.ptr = that.ptr;
        that.ptr = null;
    }
    

    which appears to act like the current type has the same members as those of its argument. So, I assume that what you're actually trying to do here is a copy constructor / move constructor for UniquePtr and not a constructor for an arbitrary type that takes a UniquePtr!T. And if that's what you're doing, then you'd want a postblit constructor - this(this) - and not one that takes the same type as the struct itself (since D does not have copy constructors). So, if what you want is a copy constructor, then you do something like

    this(this)
    {
        // Do any deep copying you want here. e.g.
        arr = arr.dup;
    }
    

    But if a bitwise copy of your struct's elements works for your type, then you don't need a postblit constructor. But moving is built-in, so you don't need to declare a move constructor regardless (a move will just blit the struct's members). Rather, if what you want is to guarantee that the object is moved and never copied, then what you want to do is disable the struct's postblit constructor. e.g.

    @disable this(this);
    

    Then any and all times that you pass a UniquePtr!T anywhere, it's guaranteed to be a move or a compilation error. And while I would have thought that you might have to disable opAssign separately to disable assignment, from the looks of it (based on the code that I just tested), you don't even have to disable assignment separately. Disabling the postblit constructor also disables the assignment operator. But if that weren't the case, then you'd just have to disable opOpAssign as well.