最近,我碰到了这个answer,它描述了如何初始化一个std::array
的非默认可构造元素。我并不感到惊讶,因为该答案显然不会进行任何默认构造。
相反,它将使用聚合初始化构造一个临时std::array
,然后移动(如果可以使用move构造函数)或在函数返回时复制到命名变量中。因此,我们只需要移动构造函数或复制构造函数即可。
或者我以为...
然后出现了这段使我感到困惑的代码:
struct foo {
int x;
foo(int x) : x(x) {}
foo() = delete;
foo(const foo&) = delete;
foo& operator=(const foo&) = delete;
foo(foo&&) = delete;
foo& operator=(foo&&) = delete;
};
foo make_foo(int x) {
return foo(x);
}
int main() {
foo f = make_foo(1);
foo g(make_foo(2));
}
所有五个特殊成员的构造函数/运算符都被明确删除,所以现在我不应该从返回值构造对象,对吗?
错误。
令我惊讶的是,它在gcc中编译(使用C ++ 17)!
为什么要编译?显然,要从函数foo
返回一个make_foo()
,我们必须构造一个foo
。这意味着在main()
函数中,我们从返回的foo
分配或构造一个foo
。那怎么可能?!
答案 0 :(得分:29)
欢迎来到guaranteed copy elision的美好世界(C ++ 17的新功能。另请参见this question)。
foo make_foo(int x) {
return foo(x);
}
int main() {
foo f = make_foo(1);
foo g(make_foo(2));
}
在所有这些情况下,您都是从类型foo
的prvalue初始化foo
的,所以我们只是忽略所有中间对象,而直接从实际的初始化程序初始化最外面的对象。这完全等同于:
foo f(1);
foo g(2);
我们甚至不在这里考虑移动构造函数-因此删除它们的事实并不重要。具体规则为[dcl.init]/17.6.1-在这之后的 ,我们才考虑构造函数并执行重载解析。
答案 1 :(得分:0)
请注意,在C ++ 17之前的版本中(在保证复制省略之前),您可能已经使用braced-init-lists返回了该对象:
foo make_foo(int x) {
return {x}; // Require non explicit foo(int).
// Doesn't copy/move.
}
但是用法会有所不同:
foo&& f = make_foo(1);
foo&& g(make_foo(2));