本地栈变量成员的返回值优化

3

我有一段通用代码,实例化后可以简化为:

struct A{...};

A f1(){
    A ret;
    std::pair<A&, int> p(ret, 1);
    p = g(); // g returns a pair<A, int>, but I am not interested in g
    return ret; // RVO :)
};

据我了解,这将与RVO一起使用。
问题是,这段其他代码是否会返回类型为A的对象,并启用RVO?
A f2(){
    std::pair<A, int> p;
    p = g();
    return p.first; // RVO ?
};

我知道由于返回对象被遮蔽,它将不会执行RVO或者编译器可能无法选择“优化”。但我并没有看到它不能实现的根本原因,换句话说,为了保持一致性,它应该执行RVO(这是我想要的)。
我之所以问这个问题,是因为我觉得f2f1更优雅。
这个最后版本是否应该执行RVO?如果是,那么它是否取决于编译器?
我的粗略测试使用gcc 8.1表明,在下面的f2f3中,RVO不起作用。
struct A{
    double something;
    A() = default;
    A(A const& other) : something(other.something){std::cerr << "cc" << '\n';}
    A(A&& other) : something(other.something){std::cerr << "mc" << '\n';}
};

A f1(){
    A ret;
    std::pair<A&, int> p(ret, 1);
    p.first.something = 5.;
    return ret; // RVO :)
};

A f2(){
    std::pair<A, int> p;//(ret, 1);
    p.first.something = 5.;
    return p.first; // no RVO :(
};

A f3(){
    std::pair<A, int> p;//(ret, 1);
    p.first.something = 5.;
    return std::move(p).first; // no RVO :(
};


int main(){
    A a1 = f1(); // prints nothing, RVO
    A a2 = f2(); // prints "cc"; no RVO! Why?
    A a3 = f3(); // prints "mc" (no much gain in this case); still no RVO!
}

1
这种情况通常被称为NRVO。 - M.M
1
你能得到的最好结果是 return std::move(p.first); - M.M
1个回答

5

RVO需要在调用者期望找到返回值的存储位置中实例化返回值。可能是由调用约定指定名称的寄存器,也可能是在堆栈上。

std::pair<A, int>本质上是:

struct foo {
    A first;
    int second;
};

现在,如果需要将返回值存储在一个大小为sizeof(A)的特定位置,但是该pair的大小比这个位置还要大,那么该pair就无法被存储在那里。如果调用者知道函数执行时返回值后面的sizeof(int)字节可以被修改,那么RVO仍然可以起作用。但是,语言可能不应该在这种情况下要求使用RVO,因为它可能无法在每种调用约定中实现。

这些规则是否被标准所覆盖?它很有道理,但我只是好奇... - Daniel Langr
1
C++标准通常不涵盖实现细节,但它是以可实现的知识为基础编写的(通常使用现有实现作为证明)。你所希望的RVO可能无法在所有平台上实现。 - John Zwinck
可以理解。我想我可以添加一个情况细化,即返回的元素是类的第一个成员。这样它就可以在堆栈的开头了。 - alfC

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