【发布时间】:2021-10-23 06:30:30
【问题描述】:
我的问题源于对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、发布)
Constructed 0000000C0BD4F990
Constructed 0000000C0BD4F991
Moved 0000000C0BD4F992
我对按值返回的函数中的 return 语句的一般初始化规则以及在返回带有 std::move 的局部变量时适用哪些规则感兴趣,如上所示。
一般情况
关于return statements你可以阅读
- 计算表达式,终止当前函数并将表达式的结果隐式转换为函数返回类型后返回给调用者。 [...]
在 cppreference.com 上。
其中Copy initialization 发生
- 从按值返回的函数返回时 像这样
return other;
回到我的示例,根据我目前的知识 - 与上述规则相反 - A a1(nrvo()); 是一个声明,它使用纯右值 nrvo() 直接初始化 a1。那么究竟哪个对象是复制初始化,如 cppreference.com 中描述的返回语句?
std::move 案
对于这种情况,我参考了 ipc 在Are returned locals automatically xvalues 上的回答。我想确保以下内容是正确的:std::move(local) 的类型为 A&&,但 no_nrvo() 被声明返回类型为 A,所以这里是
将表达式的结果隐式转换为函数返回类型后返回给调用者
部分应该发挥作用。我想这应该是Lvalue to rvalue conversion:
任何非函数、非数组类型 T 的泛左值都可以隐式转换为相同类型的纯右值。 [...] 对于类类型,此转换 [...] 将 glvalue 转换为 prvalue,其结果对象由 glvalue 复制初始化。
要从A&& 转换为A 使用A 的移动构造函数,这也是这里禁用NRVO 的原因。这些规则是否适用于这种情况,我是否理解正确?此外,他们再次说 copy-initialized 通过 glvalue 但A a2(no_nrvo()); 是直接初始化。所以这里也涉及到第一种情况。
【问题讨论】:
-
"这也是此处禁用 NRVO 的原因。"我认为你在这部分想太多了。 NRVO 并不适用,因为您返回一个临时的:
std::move()的结果,这是一个“正常”函数。它没有名字,所以 NRVO 已经出局了。 -
"...在return语句中,当操作数为非易失性对象的名称时,自动存储时长,不是函数参数或 catch 子句参数,并且与函数返回类型具有相同的类类型(忽略 cv 限定)。这种复制省略的变体被称为 NRVO,“命名返回值优化”......” "...返回值优化是强制性的,从 C++17 开始不再被视为复制省略..." en.cppreference.com/w/cpp/language/copy_elision
-
@DanielLangr 当然,std::move 的结果本身确实不是一个临时对象。考虑我使用“临时”作为简化,但一般原则仍然存在。这不是一个名字,所以 NRVO 是不行的。
-
@Frank 我一看到你的回答就明白你的意思了。删除了我的评论。只是想澄清一下,没有创建
A类型的临时。 -
@DanielLangr 这是一个公平的观点。这是一个具体的命名问题,因此 cmets/answers 应该是准确的。
标签: c++ stdmove copy-initialization direct-initialization