【发布时间】:2018-09-10 18:46:00
【问题描述】:
假设我们有一个名为AAA 的类同时支持复制/移动:
class AAA
{
public:
AAA() = default;
~AAA() = default;
AAA(const AAA& rhs)
{
std::cout << "Copy constructor" << std::endl;
}
AAA(AAA&& rhs)
{
std::cout << "Move constructor" << std::endl;
}
};
在以下代码中,get_val 返回second:
AAA get_val()
{
auto [ first, second ] = std::make_tuple(AAA{}, AAA{});
std::cout << "Returning - " << std::endl;
return second;
}
auto obj = get_val();
std::cout << "Returned - " << std::endl;
现在second 被复制,打印以下输出:
...
Returning -
Copy constructor
Returned -
这很不幸,因为我对结果的期望是要么没有调用复制构造函数,要么至少它被隐式移动了。
为避免复制,我必须在其上明确应用std::move。
return std::move(second);
然后我会收到以下结果:
...
Returning -
Move constructor
Returned -
我认为未执行 RVO 的原因可能是编译器会将second 视为参考,而get_val 返回prvalue。
但是为什么也不能期望隐式移动呢?
在这种特殊情况下,在 return 语句上使用显式 std::move 看起来并不直观,因为 you generally don't want to make RVO, which is in most cases a better optimization than move, accidentally gone away。
由编译器 gcc 和 clang 用-O3 测试。
【问题讨论】:
-
NRVO 显然是不可能的:这需要在调用上下文中构造对象,但实际构造是
tuple,而不是 AAA。名称first, second指的是元组的元素。目前,“移动上下文”是 NRVO 的一个子集,但我看不出为什么不能为非参考结构化绑定的成员添加移动上下文。但是手动编写std::move(second)是安全的,因为没有 NRVO,所以这不可能是“悲观移动”的情况 -
@MM 对。我得到了 rvo 无法执行的部分,并且手动应用
std::move使其安全。但为什么呢?让编译器为我们确保这一点会很好。我想知道标准中是否有任何引用阻止编译器应用隐式std::move。 -
标准规定这种“隐式移动”仅适用于“命名具有自动存储持续时间的对象的 id 表达式”(参见 [class.copy.elision]/3.1)。这不适用于结构化绑定,这些名称将结果命名为
std::get。我将您的问题解释为询问其背后的基本原理 -
@M.M 我不明白为什么您的报价会阻止省略。 [dcl.struct.bind]/3 中的最后一句非常混乱。看来这里
second本身应该是一个副本,因此满足“自动存储持续时间”。 -
@liliscent in
auto [b] = e;,绑定绑定到e的副本。您似乎混淆了,e与标准提到的e不同(绑定绑定到的隐藏对象)。这与这个问题中的代码无关。 , 其中一个纯右值被绑定到
标签: c++ c++11 c++17 rvo structured-bindings