2

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.

Maik Klein
  • 15,548
  • 27
  • 101
  • 197

3 Answers3

5

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.

Community
  • 1
  • 1
Jonathan M Davis
  • 37,181
  • 17
  • 72
  • 102
  • What I wanted is to disable the copy constructor because I need to ensure uniqueness but I still need a move constructor so that I can null the ptr of the rvalue. Afaik `this(UniquePtr!T that)` is the only syntax to achieve this. – Maik Klein Feb 01 '16 at 03:41
  • Why would you need to null the pointer? There is no other pointer to null. The object was moved, so there's only one object, not two. The one you want to null doesn't exist anymore. The only case I can think of where nulling the pointer would make sense would be when you were transferring ownership between two lvalues, which would be assignment or copying (though copying won't really allow it, because you don't have access to the original in the postblit, just references to the same data for any reference type members). In this case, ownership transference should probably be its own function. – Jonathan M Davis Feb 01 '16 at 04:05
3

JMD answer covers theoretical part of move semantics, I can extend it with a very simplified example implementation:

struct UniquePtr(T)
{
    private T* ptr;

    @disable this(this);

    UniquePtr release()
    {
        scope(exit) this.ptr = null;
        return UniquePtr(this.ptr);
    }
}

// some function that takes argument by value:
void foo ( UniquePtr!int ) { }

auto p = UniquePtr!int(new int);
// won't compile, postblit constructor is disabled
foo(p);
// ok, release() returns a new rvalue which is
// guaranteed to be moved without copying
foo(p.release());
// release also resets previous pointer:
assert(p.ptr is null);
Mihails Strasuns
  • 3,783
  • 1
  • 18
  • 21
1

I think I can answer it myself. Quoting the "The Programming Language":

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).

If I understood it correctly, this means that this(Struct that) will never be a copy, because it only accepts rvalues in the first place.

Maik Klein
  • 15,548
  • 27
  • 101
  • 197
  • It seems to me that's not quite the same thing as move construction, and more similar to the copy elision mechanic in C++ that was also present before move constructors were a thing. – Cubic Feb 01 '16 at 13:52