在 return 函数语句中初始化 return 按值

Initialization in return statements of functions that return by-value

我的问题源于深入研究 return 语句中的 std::move,例如以下示例:

struct A
{
    A() { std::cout << "Constructed " << this << std::endl; }
    A(A&&) noexcept { std::cout << "Moved " << this << std::endl; }
 };

A nrvo()
{
    A local;
    return local;
}

A no_nrvo()
{
    A local;
    return std::move(local);
}

int main()
{
    A a1(nrvo());
    A a2(no_nrvo());
}

打印 (MSVC, /std:c++17, release)

Constructed 0000000C0BD4F990
Constructed 0000000C0BD4F991
Moved 0000000C0BD4F992

我对 return 按值 return 函数中语句的一般初始化规则感兴趣,以及当 return 使用 [=15= 局部变量时应用哪些规则] 如上图

一般情况

关于return statements你可以阅读

  1. Evaluates the expression, terminates the current function and returns the result of the expression to the caller after implicit conversion to the function return type. [...]

于 cppreference.com。

其中 Copy initialization 发生

  1. when returning from a function that returns by value like so
 return other;

回到我的示例,根据我目前的知识 - 与上述规则相反 - A a1(nrvo());direct-initializes a1 的声明与纯右值 nrvo()。那么哪个对象正是 copy-initialized,如 cppreference.com 中描述的 return 语句?

std::move案例

对于这种情况,我参考了 ipc 在 Are returned locals automatically xvalues 上的回答。我想确保以下内容是正确的:std::move(local) 具有类型 A&&no_nrvo() 被声明为 return 类型 A,所以这里

returns the result of the expression to the caller after implicit conversion to the function return type

部分应该发挥作用。我认为这应该是 Lvalue to rvalue conversion:

A glvalue of any non-function, non-array type T can be implicitly converted to a prvalue of the same type. [...] For a class type, this conversion [...] converts the glvalue to a prvalue whose result object is copy-initialized by the glvalue.

A&&转换为A A的移动构造函数被使用,这也是为什么NRVO在这里被禁用的原因。这些规则是否适用于这种情况,我是否正确理解了它们?此外,他们再次说 copy-initialized 由 glvalue 但 A a2(no_nrvo()); 是直接初始化。所以这也涉及到第一种情况。

在深入了解这些细节时,您必须小心 cppreference.com,因为它不是权威来源。

So which object exactly is copy-initialized as described at cppreference.com for return statements?

在这种情况下,none。这就是复制省略:跳过通常会发生的复制。 cppreference (4) 子句可以写成“当 return 从按值 returns 的函数中调用时,并且副本未被删除”,但这有点多余。 standard: [stmt.return] 在这个问题上更清楚了。

To convert from A&& to A A's move constructor is used, which is also why NRVO is disabled here. Are those the rules that apply in this case, and did I understand them correctly?

不太正确。 NRVO 仅适用于非易失性对象的名称。但是,在 return std::move(local); 中,local 不是 returned,而是调用 std::move() 的结果 A&&。它没有名称,因此强制性 NRVO 不适用。

I think this should be an Lvalue to rvalue conversion:

std::move() 编辑的 A&& return 绝对不是左值。它是一个 xvalue,因此已经是一个 rvalue。这里没有左值到右值的转换。

but A a2(no_nrvo()); is a direct initialization. So this also touches on the first case.

不是真的。作为 return 语句的一部分,函数是否必须执行其结果的复制初始化不受该函数调用方式的任何影响。同样,函数的 return 参数在调用点的使用方式不受函数定义的影响。

在这两种情况下,an 都是由函数的结果直接初始化的。实际上,这意味着编译器将为 an 对象使用与函数的 return 值相同的内存位置。

A a1(nrvo());中,由于NRVO,分配给local的内存位置与函数的结果值相同,恰好已经是a1。实际上,locala1 一直都是同一个对象。

A a2(no_nrvo()) 中,local 有自己的存储空间,函数的结果,又名 a2 是从它移动构造的。实际上,local 被移动到 a2