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

时间:2015-07-21 18:07:04

标签: c++11 copy-constructor move-constructor return-by-reference return-by-value

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

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

2 个答案:

答案 0 :(得分:3)

execute(script)返回一个左值。你不能隐含地从左值移动,所以要使用你需要说的返回对象的移动构造函数

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

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

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

return thread;

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

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

答案 1 :(得分:2)

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

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

正在尝试从左值引用构建ScriptThreadexecute返回ScriptThread&)。这将调用复制构造函数,该构造函数将被删除,从而导致错误。

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

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

我们从[class.copy]:

进入规则
  

当满足复制/移动操作的省略标准时,但不符合例外声明,并且   要复制的对象由左值,指定,或者当返回语句中的表达式为(可能是   括号内的 id-expression ,用于命名具有在正文中声明的自动存储持续时间的对象或   最里面的封闭函数的 parameter-declaration-clause lambda-expression ,重载解析   选择复制的构造函数首先执行,就好像该对象是由右值指定

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

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