为什么在返回参数时不允许RVO

Why is RVO disallowed when returning a parameter?

本文关键字:不允许 RVO 参数 返回 为什么      更新时间:2023-10-16

如[C++11:12.8/31]所述:

这种复制/移动操作的省略,称为复制省略,是允许的[…]:

--在具有类返回类型的函数中的返回语句中,当表达式是与函数返回类型具有相同cv不合格类型的非易失性自动对象(而不是函数或catch子句参数)的名称时,可以通过将自动对象直接构造到函数的返回值中来省略复制/移动操作

这意味着

#include <iostream>
using namespace std;
struct X
{
    X() { }
    X(const X& other) { cout << "X(const X& other)" << endl; }
};
X no_rvo(X x) {
    cout << "no_rvo" << endl;
    return x;
}
int main() {
    X x_orig;
    X x_copy = no_rvo(x_orig);
    return 0;
}

将打印

X(const X& other)
no_rvo
X(const X& other)

为什么需要第二个副本构造函数?难道编译器不能简单地延长x的寿命吗?

假设no_rvo定义在与main不同的文件中,因此在编译main时,编译器将只看到声明

X no_rvo(X x);

并且不知道返回的类型为X的对象是否与参数具有任何关系。据当时所知,no_rvo的实现也可以是

X no_rvo(X x) { X other; return other; }

因此,当它例如编译线路时

X const& x = no_rvo(X());

当最大限度地优化时,它将执行以下操作。

  • 生成要作为参数传递给no_rvo的临时X
  • 调用no_rvo,并将其返回值绑定到x
  • destruct它传递给no_rvo的临时对象

现在,如果no_rvo的返回值与传递给它的对象是同一个对象,那么销毁临时对象就意味着销毁返回的对象。但这是错误的,因为返回的对象绑定到引用,因此将其生存期延长到该语句之后。然而,简单地不破坏参数也不是解决方案,因为如果no_rvo的定义是我上面展示的替代实现,那就错了。因此,如果允许函数重用参数作为返回值,则可能会出现编译器无法确定正确行为的情况。

请注意,对于常见的实现,编译器无论如何都无法对其进行优化,因此这并不是一个很大的损失,因此它不被正式允许。还要注意的是,如果编译器能够证明这不会导致可观察行为的变化(所谓的"好像规则"),那么它就可以优化拷贝。

RVO的常见实现是调用代码传递内存块的地址,函数应在该地址构造其结果对象。

当函数结果直接是一个不是形式参数的自动变量时,该局部变量可以简单地放置在调用方提供的内存块中,然后返回语句根本不进行复制。

对于通过值传递的参数,调用机器代码必须将其实际参数复制到形式参数中;s的位置,然后再跳转到函数。为了让函数将结果放在那里,它必须首先销毁形式参数对象,这有一些棘手的特殊情况(例如,当该构造直接或间接引用形式参数对象时)。因此,这里的优化在逻辑上必须为函数结果使用一个单独的调用提供的内存块,而不是用正式的参数位置来标识结果位置。

但是,未在寄存器中传递的函数结果通常由调用者提供。也就是说,对于表示正式论点的return表达式,人们可以合理地将其称为RVO,一种减少的RVO,这是无论如何都会发生的事情。而且它不符合文本“通过将自动对象直接构造为函数的返回值";。

总之,要求调用方传入值的数据流意味着初始化正式参数存储的必然是调用方,而不是函数。因此,从形式参数复制回通常是不可避免的(这个狡猾的术语涵盖了编译器可以做非常特殊的事情的特殊情况,特别是对于内联机器代码)。然而,它是初始化任何其他本地自动对象的函数&rsquo的;的存储空间,然后;做RVO没有问题。