代码如下所示。我的想法是,我们有一个P,它是可移动的,但不可复制的。我们有一个OP,它是P的optional
Package 器。一个gen_p
将生成一个std::pair<int, OP>
,其返回值将通过结构化绑定进行“模式匹配”。然后我认为返回值被分解为i
和pp
。我希望pp
通过move或RVO返回,至少不是通过copy。
#include <iostream>
#include <vector>
#include <optional>
#include <map>
struct P {
int i;
std::vector<int> v;
P(int ii, std::vector<int>&& vv): i(ii), v(std::move(vv)) {}
P(P&& other) {
i = other.i;
v = std::move(other.v);
}
P & operator=(P && other) {
i = other.i;
v = std::move(other.v);
return *this;
}
P(const P & other) = delete;
P & operator=(const P &) = delete;
};
typedef std::optional<P> OP;
OP move_out_p() {
auto gen_p = [&](){
P pp1 {1, std::vector<int>{2,3,4}};
std::optional<P> op = std::make_optional(std::move(pp1));
return std::make_pair(1, std::move(op));
};
auto&& [i, pp2] = gen_p();
// It compiles with std::move(pp2)
return pp2;
}
static_assert(std::is_move_constructible_v<P>);
void test_move_part() {
auto v = move_out_p();
printf("v size %lu\n", v.value().v.size());
}
int main() {
test_move_part();
}
我会犯错
tm.cpp:87:12: error: call to implicitly-deleted copy constructor of 'OP' (aka 'optional<P>')
return pp2;
我觉得这个错误很奇怪。以下是我的推理:It表示
在这些初始化表达式中,如果实体e的类型是左值引用(这仅在ref限定符是&或如果它是&&并且初始化表达式是左值时发生),则e是左值,否则是xvalue(这有效地执行了一种完美的转发)。
因为我的ref-qualifier是&&
,而gen_p()
不是左值(它是actually a prvalue),所以在我的情况下,它是一个完美的转发。
我不知道在这个完美的转发案例中我会得到什么。然而,如果pp2
是一个左值,那么NRVO将发生。我确信在我的编译器中启用了NRVO,因为我们需要指定-fno-elide-constructors
来禁用它。如果pp2
是一个xvalue,它将编译。因为std::move(pp)
创建了一个xvalue,它编译了。如果pp2
是一个纯右值,我不知道,但我发现要求一个纯右值的复制构造函数是没有意义的。所以我认为没有任何情况可以阻止RVO。
此外,如果pp 2是左值,则它应该是RVO。我不明白为什么在这种情况下没有发生RVO。在我们的程序中,我们不返回std::move(pp 2),这是recommended。我不知道我们为什么要打破这个规矩。编译的代码看起来很奇怪。
所以我在这里迷路了?你能给我解释一下这个错误吗?
1条答案
按热度按时间suzh9iv81#
首先是一些术语。
RVO或NRVO是指拷贝省略的特定示例。在C中,复制省略是一种优化,其中存在语言规则说将发生复制/移动的情况,但编译器可以选择不发生复制/移动。然而,在 * 所有这些情况下 *,即使复制/移动将被省略,它仍然必须 * 雅阁语言规则是可能的 *。
注意,C17的“保证省略”是not really a form of "elision" per se;它重新定义了某些C++概念,使得没有复制/移动。但由于您的案例涉及一个命名对象,因此保证省略不适用。
所以如果你允许编译器省略一些东西也没关系;它仍然必须是可以发生在该对象上的事情。如果
return pp2;
意味着从pp2
复制,则编译器上的省略设置无关紧要。无论pp2
是什么,它必须是可复制的,否则会导致编译错误。所以唯一的问题是:为什么
return pp2;
尝试从pp2
复制,而不是从它 * 移动 ?通常,return identifier;
将从identifier
移动。但这是有规矩的。特别是:
隐式可移动实体是自动存储持续时间的变量,它是非易失性对象或对非易失性对象类型的右值引用。
那么,结构化绑定的名称是什么呢?好吧,对于通过
get<i>
调用枚举元素的结构化绑定,它们是引用。但是,如果用于初始化引用的初始化器是prvalue,则它们仅是右值引用。使用的初始化器是
get<i>(e)
,其中e
是所讨论的值。get<i>
对于pair
返回左值引用或右值引用。它 * 从不 * 返回纯右值(因为这将是对象的副本)。因此,
pair
的结构化绑定的名称永远不会是右值引用,因此它们不能在return
语句中隐式移动。return pp2;
将尝试从中复制。应该注意的是,结构化绑定的其他两种形式(数组和公共可访问的结构体成员) 也 * 永远不会允许结构化绑定的元素被隐式移动。对于结构体和数组元素,名称引用某个对象的子对象。它们不是引用,但也不是对象。因此,两者都不符合隐性变动的条件。
简而言之,您不应该期望将结构化绑定的元素隐式移动。