RVO对对象成员起作用吗?

Does RVO work on object members?

本文关键字:起作用 成员 对象 RVO      更新时间:2023-10-16

考虑以下内容:

struct A { /* ... */ };
A foo() {
  auto p = std::make_pair(A{}, 2);
  // ... do something
  return p.first;
}
auto a = foo();

p.first会被复制、移动或RVO-ed吗?

我发现在Visual Studio 2010和gcc-5.1中RVO是而不是应用(参见例如http://coliru.stacked-crooked.com/a/17666dd9e532da76)。

标准的相关章节为12.8.31.1 [class.copy]。它声明允许复制省略(我的高亮显示):

在具有类返回类型的函数的返回语句

中,当表达式是与函数返回类型相同的非易失性自动对象的名称(函数参数或由处理程序([exception .handle])的异常声明引入的变量除外)(忽略cv-限定)时,可以通过将自动对象直接构造为函数的返回值

来省略复制/移动操作。

由于p.first不是对象的名称,因此禁止使用RVO

只是添加一点燃料,如果RVO在发挥作用,该函数将如何?调用者将A的实例放在内存中的某个地方,然后调用foo对其进行赋值(更好的是,让我们假设A是一个更大结构的一部分,并且让我们假设它是正确对齐的,这样结构的下一个成员就在A的实例之后)。假设RVO在起作用,pfirst部分位于调用者想要的位置,但是intsecond的位置在哪里?它必须位于A实例的后面,以保持pair正常工作,但是在源位置,A实例的后面还有其他成员。

我希望RVO不会在这里发生,因为您只是返回一个较大对象的一部分。当first必须处于可破坏状态时,可能会发生移动

@atkins第一个找到了答案。只是添加这个小测试程序,你可能会发现在未来跟踪移动/分配行为时有用。

#include <iostream>
#include <string>
using namespace std::string_literals;
struct A {
    A()
    : history("created")
    {
    }
    A(A&& r)
    : history("move-constructed,"s + r.history)
    {
        r.history = "zombie: was "s + r.history;
    }
    A(const A& r)
    : history("copied from: " + r.history)
    {
    }
    ~A() {
        history = "destroyed,"s + history;
        std::cout << history << std::endl;
    }
    A& operator=(A&& r) {
        history = "move-assigned from " + r.history + " (was "s + history + ")"s;
        r.history = "zombie: was "s + r.history;
        return *this;
    }
    A& operator=(const A&r ) {
        history = "copied from " + r.history;
        return *this;
    }
    std::string history;
};
A foo() {
    auto p = std::make_pair(A{}, 2);
    // ... do something
    return p.first;
}

auto main() -> int
{
    auto a = foo();
    return 0;
}

示例输出:

destroyed,zombie: was created
destroyed,move-constructed,created
destroyed,copied from: move-constructed,created

考虑以下代码:

struct A {};
struct B {};
struct C { B c[100000]; };
A callee()
{
    struct S
    {
        A a;
        C c;
    } s;
    return s.a;
}
void caller()
{
    A a = callee();
    // here should lie free unused spacer of size B[100000]
    B b;
}

"部分"RVO应该导致调用方堆栈使用过度膨胀,因为(我认为)S只能完全在调用方堆栈帧中构造。

另一个问题是~S()的行为:
// a.hpp
struct A {};
struct B {};
struct C { A a; B b; ~C(); };
// a.cpp
#include "a.hpp"
~C() { /* ... */; }
// main.cpp
#include "a.hpp"
A callee()
{
    C c;
    return c.a;
} // How to destruct c partially, having the user defined ~C() in another TU?
// Even if destructor is inline and its body is visible,
// how to automatically change its logic properly?
// It is impossible in general case.
void caller() { A a = callee(); }