例如:
Big create()
{
Big x;
return std::move(x);
// return static_cast<typename std::remove_reference<T>::type&&>(t) // why not elide here?
}
假设应用std::move()
来返回局部变量会抑制移动语义,因为编译器通常不能对函数的内部工作做出任何假设,那么在不需要这些假设的情况下,例如:
std::move(x)
内联(可能总是)std::move(x)
写成:static_cast<typename std::remove_reference<T>::type&&>(t)
根据现行标准,允许实施NRVO ......
- 在具有类返回类型的函数的return语句中,时 expression是非易失性自动对象的名称(除了 函数参数或由...引入的变量 具有相同类型的处理程序(18.3)的异常声明 (忽略cv-qualification)作为函数返回类型,复制/移动 通过直接构造自动对象可以省略操作 进入函数调用的返回对象
显然,1)和2)都没有资格。除了使用std::move()
返回局部变量的事实是多余的,为什么需要这个限制?
答案 0 :(得分:2)
重读这个问题后,我的理解不同了。我将此问题视为'为什么std::move()
禁止(N)RVO'
引用问题中提供的标准有错误突出显示。它应该是
在一个带有类返回类型的函数的return语句中 expression是非易失性自动对象的名称(除了 函数参数或由...引入的变量 处理程序的异常声明(18.3))具有相同的类型 (忽略cv-qualification)作为函数返回类型
这里禁止NRVO的原因不是调用std::move()
,而是std::move
的返回值不是X
,而是X&&
。 与功能签名不匹配!
答案 1 :(得分:2)
你应该清楚知道什么&#34;允许elision&#34;手段。首先,编译器可以在&#34; as-if&#34;下进行任何想做的事情。规则。也就是说,只要该程序集行为正确,编译器就可以吐出它想要的任何程序集。这意味着编译器可以忽略它想要的任何构造函数,但无论是否调用构造函数,它都必须证明程序的行为是相同的。
那么为什么elision的特殊规则呢?好吧,这些情况下编译器可以忽略构造函数调用(因此,析构函数调用),而证明行为是相同的。这是非常有用的,因为有很多类型的构造函数非常重要(例如,string
),并且实际中的编译器通常无法证明它们是安全的(在一个合理的时间框架)(在过去,甚至还不清楚优化堆分配是否合法开始,因为它基本上是全局变量的变异)。
因此,出于性能原因,我们希望省略。但是,它基本上在行为方面指定了标准中的特殊情况。特殊情况越大,我们引入标准的复杂性就越大。所以我们的目标应该是让elision的允许情况足够广泛,以涵盖我们关心的有用案例,但不能更广泛。
你正在接近这个:为什么不把这个特殊案例变得和实际一样大?实际上,情况正好相反。为了扩展elision的允许情况,需要证明它是非常值得的。