为什么返回参数时不允许使用RVO?

Val*_*lea 21 c++ return-value-optimization copy-elision rvo

它在[C++ 11:12.8/31]中说明:

复制/移动操作的省略,称为复制省略,允许[...]:

- 在具有类返回类型的函数的return语句中,当表达式是具有与函数返回类型相同的cv-unqualified类型的非易失性自动对象(除函数或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;
}
Run Code Online (Sandbox Code Playgroud)

将打印

X(const X& other)
no_rvo
X(const X& other)
Run Code Online (Sandbox Code Playgroud)

为什么需要第二个拷贝构造函数?编译器不能简单地延长x的生命周期吗?

cel*_*chk 11

Imagine no_rvo是在一个不同的文件中定义的,main因此在编译编译main器时只会看到声明

X no_rvo(X x);
Run Code Online (Sandbox Code Playgroud)

并且不知道X返回的对象类型是否与参数有任何关系.从它知道的那一点来看,no_rvo也可以实施

X no_rvo(X x) { X other; return other; }
Run Code Online (Sandbox Code Playgroud)

所以当它例如编译线时

X const& x = no_rvo(X());
Run Code Online (Sandbox Code Playgroud)

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

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

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

请注意,对于常见的实现,编译器无论如何都无法对其进行优化,因此它不是一个没有正式允许的大损失.另请注意,如果能够证明这不会导致可观察行为发生变化(即所谓的as-if规则),允许编译器优化副本.


Che*_*Alf 5

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

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

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

然而,这不是在寄存器传递函数结果由呼叫者通常提供.也就是说,人们可以合理地谈论的是RVO,一种减少的RVO,对于return表达正式论证的表达式,无论如何都会发生.并且它不适合文本"通过将自动对象直接构造到函数的返回值".

总而言之,要求调用者传入值的数据流意味着调用者必须初始化正式参数的存储,而不是函数.因此,从形式参数复制回是无法避免的,一般(即黄鼠狼词涵盖的特殊情况下,编译器可以为内联机器代码做的很特别的东西,尤其是).但是,它是初始化任何其他本地自动对象的存储的函数,然后执行RVO没有问题.

  • 虽然通常的实现无论如何都不支持这种优化,但这本身并不能解释为什么标准禁止它.毕竟,这是一个优化,因此不需要*完成. (4认同)
  • @DavidRodríguez-dribeas:*no*current编译器是否以允许此优化的方式实现并不重要.编译器编写者可以自由地忽略允许此优化.编译器编写者也知道在这种情况下没有进行这种优化,因为编译器就是这样做的,而编译器却没有. (3认同)
  • @DavidRodríguez-dribeas:如果不可能实现,那么标准明确(!)禁止它的原因甚至会更少*.没有必要这样做,额外的括号只会浪费空间和时间.如果你明确禁止某些东西,那么你这样做是因为它*可能*,但会产生你不想要的后果. (2认同)