D有移动构造函数吗?

Does D have a move constructor?

我引用了这个 SO 答案 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.

他举了这样一个例子:

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

变量that总是会被移动吗?或者变量 that 会不会在某些情况下被复制?

如果我只将临时副本上的 ptr 设为空,那将是不幸的。

我想我可以自己回答。引用 "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).

如果我理解正确,这意味着 this(Struct that) 永远不会是副本,因为它首先只接受右值。

嗯,你也可以看看这个SO问题:

Questions about postblit and move semantics

在D中复制一个struct的方式是它的内存被blitted,然后如果它有一个postblit构造函数,它的postblit构造函数被调用。如果编译器确定实际上不需要副本,那么它就不会调用 postblit 构造函数,也不会调用原始对象的析构函数。因此,它将移动对象而不是复制对象。

特别是,根据 TDPL (p.251),语言保证

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

因此,在其他情况下,编译器可能会也可能不会删除副本,这取决于当前的编译器实现和优化级别(例如,如果您将左值传递给按值获取它的函数,并且该变量永远不会函数调用后再次引用)。

所以,如果你有

void foo(Bar bar)
{}

那么 foo 的参数是否被移动取决于它是右值还是左值。如果它是一个右值,它将被移动,而如果它是一个左值,它可能不会被移动(但可能取决于调用代码和编译器)。

所以,如果你有

void foo(UniquePtr!T ptr)
{}
如果 foo 传递了一个右值,

ptr 将被移动,如果它传递了一个左值,则可能会移动也可能不会移动(尽管通常不会)。因此,UniquePtr 的内部会发生什么取决于您如何实现它。如果 UniquePtr 禁用 postblit 构造函数使其无法被复制,则传递右值将移动参数,传递左值将导致编译错误(因为右值保证被移动,而左值不是)。

现在,你拥有的是

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

这看起来像当前类型具有与其参数相同的成员。因此,我假设您实际上在这里尝试做的是 UniquePtr 的复制构造函数/移动构造函数,而不是采用 UniquePtr!T 的任意类型的构造函数。如果那是你正在做的,那么你需要一个 postblit 构造函数 - this(this) - 而不是一个与结构本身采用相同类型的构造函数(因为 D 没有复制构造函数)。所以,如果你想要的是一个复制构造函数,那么你可以做类似

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

但是如果结构元素的按位副本适用于您的类型,那么您就不需要 postblit 构造函数。但是移动是内置的,因此无论如何您都不需要声明移动构造函数(移动只会 blit 结构的成员)。相反,如果你想要的是保证对象被移动并且永远不会被复制,那么你想要做的就是禁用结构的 postblit 构造函数。例如

@disable this(this);

然后任何时候你在任何地方传递 UniquePtr!T ,它肯定是移动或编译错误。虽然我认为您可能必须单独禁用 opAssign 才能禁用赋值,但从它的外观(基于我刚刚测试的代码)来看,您甚至不必单独禁用赋值。禁用 postblit 构造函数也会禁用赋值运算符。但如果情况并非如此,那么您还必须禁用 opOpAssign

JMD 回答涵盖了移动语义的理论部分,我可以用一个非常简化的示例实现来扩展它:

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