为什么RVO要求如此严格?

14
又一个关于“为什么必须使用std :: move防止(未命名的)返回值优化?”的问题: 为什么std :: move会防止RVO?解释了标准明确要求函数声明的返回类型必须与return语句中表达式的类型相匹配。这解释了符合规范的编译器的行为;但是,它并不能解释限制的原理。
为什么RVO的规则不针对函数的返回类型为Treturn表达式的类型为T&&的情况做出异常?
我也知道在编译器中实现这样的功能并不是免费的。我建议只允许这样的例外,但不强制执行。
我也知道return std::move(...)是不必要的,因为自C++11以来当无法应用RVO时已经需要使用移动语义。尽管如此,为什么不容忍显式请求进行优化而将其转换为悲观结果?
(旁注:为什么return-value-optimizationrvo标签不是同义词?)

自C++11起,当无法应用RVO时,已经要求使用移动语义。这种说法有点奇怪。我更愿意总结为“C++11已经要求在RVO适用的条件下使用移动语义,但编译器没有实现它。”;特别是,如果您尝试执行return x.y;,其中x是一个局部变量,则将是一个没有std::move(或不同的rvalue转换)的副本。 - Arne Vogel
1个回答

19
auto foo() -> T&&;

auto test() -> T
{
    return foo();
}

你说在这种情况下应该允许使用RVO。但是考虑到这个foo的合法实现:
T val;

auto foo() -> T&&
{
   return static_cast<T&&>(val); // because yes, it's legal
}

教训是:只有使用prvalues,你才能确切地知道你拥有一个临时对象,并且你最重要的是,你知道了这个临时对象的生命周期,因此你可以省略它的构造和销毁。但是,对于xvalues(例如T&&返回值),你不知道它是否真的绑定到一个临时对象,你不知道该值何时创建、何时退出作用域,甚至如果你知道,也不能像上面的例子那样更改它的构造和销毁点。
引用块:
我不确定我完全理解。如果RVO可以应用于test(),那为什么这比test执行T temp = foo(); return temp;更糟,这将允许NRVO?
并不是说这更糟。只是不可能。在您的示例中,temp是一个局部变量,在您想要应用NRVO的函数中,即test。因此,它是一个对象,在test的上下文中完全“已知”,它的生命周期已知,正常的构造和析构点也已知。因此,它不是在test的堆栈帧中创建temp变量,而是在调用者的堆栈帧中创建temp变量。这意味着没有将对象从test的堆栈帧复制到调用者的堆栈帧。此外,请注意,在此示例中,foo()是完全无关紧要的。它可以是temp的任何初始化内容。
auto test() -> T
{
    T temp = /*whatever*/;

    return temp; // NRVO allowed
}

但是使用return foo()语句,你不能省略复制操作,因为你不知道返回的引用指向哪个对象。它可能是任何对象的引用。


我不确定我完全理解。如果允许将 RVO 应用于“test()”,为什么这会比“test”执行以下操作更糟糕:“T temp = foo(); 返回 temp;” 这将允许 NRVO? - jamesdlin
这是否意味着通过值返回比使用移动语义返回更有效?我的理解是,RVO可能会将返回实现为参数引用。填充此引用对象比移动对象更快,对吗? - Zebrafish
@Zebrafish 通过复制省略直接构造对象避免了复制和移动操作,因此,是比执行移动操作更高效的。 - jamesdlin
@bolov:请求:您能否前往rvo标签的同义词页面,并建议将return-value-optimization作为同义词?正如jamesdlin在他的问题中指出的那样,它们不是同义词有点傻,但当然,由于它们没有经常使用,很少有人既有声望又有答案得分来首先建议同义词。 - ShadowRanger
1
@ShadowRanger做不到这一点,因为返回值优化被建议作为省略复制的同义词,而在我看来这是不正确的。我已打开了元(有点),并回答了一个旧问题:https://meta.stackoverflow.com/a/371346/2805305 - bolov
@bolov:够好了。感谢您的关注! - ShadowRanger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接