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

C++11 在没有复制构造函数的情况下返回对*的引用?,c++11,copy-constructor,move-constructor,return-by-reference,return-by-value,C++11,Copy Constructor,Move Constructor,Return By Reference,Return By Value,我编写了一个类似于以下内容的类: 类脚本线程{ 公众: ScriptThread():mParent(){} 私人: ScriptThread(ScriptThread*父级):mParent(父级){} 公众: ScriptThread(ScriptThread&&rhs); ScriptThread&operator=(ScriptThread&rhs); //复制隐式删除的构造函数/赋值 脚本线程和执行(常量脚本和脚本); ScriptThread spawn(); 脚本线程生成(常量脚本

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

类脚本线程{
公众:
ScriptThread():mParent(){}
私人:
ScriptThread(ScriptThread*父级):mParent(父级){}
公众:
ScriptThread(ScriptThread&&rhs);
ScriptThread&operator=(ScriptThread&rhs);
//复制隐式删除的构造函数/赋值
脚本线程和执行(常量脚本和脚本);
ScriptThread spawn();
脚本线程生成(常量脚本和脚本);
私人:
脚本线程*mParent;
};
脚本线程和脚本线程::执行(常量脚本和脚本){
//开始执行给定的脚本
归还*这个;
}
ScriptThread脚本线程::spawn(){
//创建一个以“this”作为其父级的ScriptThread
返回脚本线程(this);
}
ScriptThread ScriptThread::spawn(常量脚本和脚本){
//同时生成和执行的方便方法
return spawn().execute(script);//错误:“使用已删除的函数”
}
如前所述,g++未能在标记为“ERROR”的行处编译它,声称它试图使用(已删除的)复制构造函数。但是,如果我将最后一个函数替换为:

ScriptThread ScriptThread::spawn(常量脚本和脚本){
ScriptThread线程=spawn();
执行(脚本);
返回线程;
}

它编译时没有错误。即使在参考了许多文章、参考文献和其他问题之后,我也不明白:为什么第一个调用复制构造函数?移动构造函数还不够吗?

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

ScriptThread ScriptThread::spawn(const Script &script) {
    return spawn().execute(script);
}
正在尝试从左值引用构造
ScriptThread
execute
返回
ScriptThread&
)。这将调用复制构造函数,该构造函数将被删除,因此会出现错误

但是,在第二次尝试中:

ScriptThread ScriptThread::spawn(const Script &script) {
    ScriptThread thread = spawn();
    thread.execute(script);
    return thread;
}
我们在[class.copy]中遇到了规则:

当满足省略复制/移动操作的条件,但不满足异常声明的条件时 要复制的对象由左值指定,或者当return语句中的表达式是 括号中)id表达式,用于命名在正文中声明了自动存储持续时间的对象或 最内层封闭函数或lambda表达式的参数声明子句,重载解析 首先执行为副本选择构造函数的操作,就像对象由右值指定一样

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


这就是为什么替换是有效的(并且使用move构造),但是原始版本无法编译(因为它需要复制构造)

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

ScriptThread ScriptThread::spawn(const Script &script) {
    return spawn().execute(script);
}
正在尝试从左值引用构造
ScriptThread
execute
返回
ScriptThread&
)。这将调用复制构造函数,该构造函数将被删除,因此会出现错误

但是,在第二次尝试中:

ScriptThread ScriptThread::spawn(const Script &script) {
    ScriptThread thread = spawn();
    thread.execute(script);
    return thread;
}
我们在[class.copy]中遇到了规则:

当满足省略复制/移动操作的条件,但不满足异常声明的条件时 要复制的对象由左值指定,或者当return语句中的表达式是 括号中)id表达式,用于命名在正文中声明了自动存储持续时间的对象或 最内层封闭函数或lambda表达式的参数声明子句,重载解析 首先执行为副本选择构造函数的操作,就像对象由右值指定一样

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

这就是为什么替换是有效的(并且使用move构造),但是原始版本无法编译(因为它需要复制构造)

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

return std::move(spawn().execute(script));
您没有这样做,所以它尝试使用复制构造函数,因为这就是从左值生成新对象的方式

在替换案例中,您有:

return thread;
这里的
thread
也是一个左值,但一旦函数结束,它就将超出范围,因此从概念上讲,它可以被视为一个临时变量或其他变量,将在表达式末尾消失。因此,C++标准中有一个特殊的规则,即编译器把这些局部变量当作rVals,允许使用移动构造函数,即使<代码>线程< /> >实际上是一个LVal.< /P> 有关定义特殊规则的标准的引用以及规则的完整细节,请参见Barry更完整的回答。

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

return std::move(spawn().execute(script));
您没有这样做,所以它尝试使用复制构造函数,因为这就是从左值生成新对象的方式

在替换案例中,您有:

return thread;
这里的
thread
也是一个左值,但一旦函数结束,它就将超出范围,