0

我写了一个类似于以下的类:

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 问题,我也不明白:为什么第一个调用复制构造函数?移动构造函数还不够吗?

4

2 回答 2

3

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

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

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

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

return thread;

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

有关定义特殊规则的标准的引用以及规则的完整详细信息,请参阅 Barry 的更完整答案。

于 2015-07-21T18:32:37.527 回答
2

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

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

正在尝试从左值引用构造 a ScriptThread返回a executeScriptThread&。这将调用复制构造函数,该构造函数被删除,因此出现错误。

但是,在您的第二次尝试中:

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

我们遇到了来自 [class.copy] 的规则:

当满足复制/移动操作的省略条件但不满足异常声明时,并且要复制的对象由左值指定,或者当返回语句中的表达式是(可能是括号)id-使用在最内层封闭函数或lambda-expression主体或 参数声明子句中声明的自动存储持续时间命名对象的表达式,首先执行为复制选择构造函数的重载决策,就好像该对象由右值指定一样.

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

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

于 2015-07-21T18:15:56.207 回答