4

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

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

我对按值返回的函数中返回语句的一般初始化规则以及在返回局部变量时适用哪些规则感兴趣,std::move如上所示。

一般情况

关于退货声明,您可以阅读

  1. 计算表达式,终止当前函数,并在隐式转换为函数返回类型后将表达式的结果返回给调用者。[...]

在 cppreference.com 上。

其中复制初始化发生

  1. 当从一个按值返回的函数返回时
 return other;

回到我的例子,根据我目前的知识 - 与上述规则相反 -A a1(nrvo());是一个使用 prvalue直接初始化nrvo()a1 的语句。那么,如 cppreference.com 中针对返回语句所述,哪个对象被复制初始化了呢?

std::move案例_

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

将表达式的结果隐式转换为函数返回类型后返回给调用者

部分应该发挥作用。我认为这应该是左值到右值的转换

任何非函数、非数组类型 T 的泛左值都可以隐式转换为相同类型的纯右值。[...] 对于类类型,此转换 [...] 将 glvalue 转换为 prvalue,其结果对象由 glvalue 复制初始化。

使用to convert from A&&toA A的移动构造函数,这也是这里禁用 NRVO 的原因。这些规则是否适用于这种情况,我是否理解正确?此外,他们再次说由 glvalue复制初始化A a2(no_nrvo());,但它是直接初始化。所以这也涉及到第一种情况。

4

1 回答 1

2

You have to be careful with cppreference.com when diving into such nitty-gritty, as it's not an authoritative source.

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

In this case, none. That's what copy elision is: The copy that would normally happen is skipped. The cppreference (4) clause could be written as "when returning from a function that returns by value, and the copy is not elided", but that's kind of redundant. The standard: [stmt.return] is a lot clearer on the subject.

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?

That's not quite right. NRVO only applies to names of non-volatile objects. However, in return std::move(local);, it's not local that is being returned, it's the A&& that is the result of the call to std::move(). This has no name, thus mandatory NRVO does not apply.

I think this should be an Lvalue to rvalue conversion:

The A&& returned by std::move() is decidedly not an Lvalue. It's an xvalue, and thus an rvalue already. There is no Lvalue to rvalue conversion happening here.

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

Not really. Whether a function has to perform copy-initialization of its result as part of a return statement is not impacted in any way by how that function is invoked. Similarly, how a function's return argument is used at the callsite is not impacted by the function's definition.

In both cases, an is direct-initialized by the result of the function. In practice, this means that the compiler will use the same memory location for the an object as for the return value of the function.

In A a1(nrvo());, thanks to NRVO, the memory location assigned to local is the same as the function's result value, which happens to be a1 already. Effectively, local and a1 were the same object all along.

In A a2(no_nrvo()), local has its own storage, and the result of the function, aka a2 is move-constructed from it. Effectively, local is moved into a2.

于 2021-08-22T18:00:22.843 回答