C++ 为什么返回参数时不允许RVO?

C++ 为什么返回参数时不允许RVO?,c++,return-value-optimization,copy-elision,rvo,C++,Return Value Optimization,Copy Elision,Rvo,[C++11:12.8/31]中规定: 允许省略复制/移动操作,称为复制省略[…]: -在具有类返回类型的函数中的return语句中,当表达式是非易失性自动对象(而不是函数或catch子句参数)的名称时,该对象的cv非限定类型与函数返回类型相同,通过将自动对象直接构造到函数的返回值中,可以省略复制/移动操作 这意味着 #include <iostream> using namespace std; struct X { X() { } X(const X&

[C++11:12.8/31]中规定:

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

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

这意味着

#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的生存期吗?

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

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

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

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


总之,要求调用者传入值的数据流意味着初始化形式参数存储的必然是调用者,而不是函数。因此,通常无法避免从形式参数复制回来(weasel术语包括编译器可以做非常特殊的事情的特殊情况,特别是对于内联机器代码)。但是,它是初始化任何其他本地自动对象存储的函数,然后执行RVO是没有问题的。

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


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

在该示例中,实际上有三个对象(可能)构造了副本(指向
no\u rvo
的参数,
no\u rvo
x\u copy
的返回值)。可以省略
x\u copy
的构造(通过直接在
x\u copy
中构造
no\u rvo
的返回值)@Mankarse:我想这正是OP所要问的-为什么不省略
x_copy
的构造?@jweyrich:不-问题是为什么不省略返回值的构造。我的观点是,说代码将打印
x(const x和其他)No_rvo x(const x和其他)是不准确的
,因为代码也可以打印
X(常数X和其他)no\rvo X(常数X和其他)X(常数X和其他)
如果
x_copy
的构造没有被省略。@Mankarse:Ops,没错。我现在明白你的意思了。它是/will/may/print。虽然公共实现确实不支持这种优化,但这本身并不能解释为什么标准禁止它。毕竟,它是一种优化,因此不需要已完成。@celtschk它不仅是通用实现,而且是所有现有实现。还要注意,此优化不能在函数内部完成,而必须由调用方在调用约定的支持下执行。请注意,只有调用方知道参数是右值且空格还请注意,调用者和被调用者必须在每次调用的基础上就参数在函数调用期间是否会在语义上被破坏或是否会被破坏达成一致
X const& x = no_rvo(X());