Return 在没有复制构造函数的情况下引用 *this?

Return reference to *this without a copy constructor?

我写了一个 class 类似于以下内容:

class ScriptThread {
public:
    ScriptThread(): mParent() {}

private:
    ScriptThread(ScriptThread *parent): mParent(parent) {}

public:
    ScriptThread(ScriptThread &&rhs);
    ScriptThread &operator = (ScriptThread &&rhs);
    // copy constructor/assignment deleted implicitly

    ScriptThread &execute(const Script &script);
    ScriptThread spawn();
    ScriptThread spawn(const Script &script);

private:
    ScriptThread *mParent;
};

ScriptThread &ScriptThread::execute(const Script &script) {
    // start executing the given script
    return *this;
}

ScriptThread ScriptThread::spawn() {
    // create a ScriptThread with "this" as its parent
    return ScriptThread(this);
}

ScriptThread ScriptThread::spawn(const Script &script) {
    // convenience method to spawn and execute at the same time
    return spawn().execute(script); // ERROR: "use of deleted function"
}

正如所写,g++ 无法在标记为 "ERROR" 的行编译它,声称它正在尝试使用(已删除的)复制构造函数。但是,如果我用这个替换最后一个函数:

ScriptThread ScriptThread::spawn(const Script &script) {
    ScriptThread thread = spawn();
    thread.execute(script);
    return thread;
}

编译没有错误。即使在参考了许多文章、参考资料和其他 SO 问题之后,我还是不明白:为什么 first 根本调用复制构造函数?移动构造函数还不够吗?

ScriptThread 是不可复制的(隐式复制 constructor/assignment 运算符被定义为已删除,因为您声明了移动 constructor/assignment)。在 spawn() 中,您的原始实现:

ScriptThread ScriptThread::spawn(const Script &script) {
    return spawn().execute(script);
}

正试图从 左值 引用(execute returns 和 ScriptThread&)构造一个 ScriptThread。这将调用被删除的复制构造函数,因此会出现错误。

然而,在您的第二次尝试中:

ScriptThread ScriptThread::spawn(const Script &script) {
    ScriptThread thread = spawn();
    thread.execute(script);
    return thread;
}

我们运行进入规则,来自[class.copy]:

When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

即使 thread 是左值,我们对 ScriptThread 的构造函数执行重载决策,就好像它是右值一样。对于这种情况,我们确实有一个有效的构造函数:您的移动 constructor/assignment。

这就是为什么替换有效(并使用移动构造),但原始编译失败(因为它需要复制构造)。

execute(script) returns 一个左值。您不能从左值隐式移动,因此要对返回的对象使用移动构造函数,您需要说

return std::move(spawn().execute(script));

你没有这样做所以它尝试使用复制构造函数,因为这就是你从左值创建新对象的方式。

在您的替换案例中,您有:

return thread;

这里thread也是一个左值,但是函数一结束就要出作用域,所以概念上可以认为是像将在表达式末尾消失的临时变量或其他变量。因此,C++ 标准中有一条特殊规则,表示编译器将此类局部变量视为右值,允许使用移动构造函数 即使 thread 实际上是一个左值.

请参阅 Barry 更完整的回答,了解对定义特殊规则的标准的引用,以及该规则的完整详细信息。