Class 删除了所有自动生成的 constructors/operators 仍然可以从函数返回吗?
Class with all automatically-generated constructors/operators deleted can still be returned from a function?
最近,我遇到了这个 ,它描述了如何初始化一个 std::array
非默认构造元素。我并不感到惊讶,因为这个答案显然没有进行任何默认构造。
相反,它使用聚合初始化构造一个临时 std::array
,然后在函数 return 时移动(如果移动构造函数可用)或复制到命名变量中。所以我们只需要移动构造函数或复制构造函数可用。
大概我是这么想的...
然后就出现了这段代码,把我搞糊涂了:
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));
}
所有五个特殊成员 constructors/operators 都被显式删除,所以现在我不应该能够从 return 值构造我的对象,对吗?
错了。
令我惊讶的是,这在 gcc 中编译(使用 C++17)!
为什么要编译?显然从函数make_foo()
到return一个foo
,我们要构造一个foo
。这意味着在 main()
函数中,我们正在从 returned foo
中分配或构造一个 foo
。这怎么可能?!
欢迎来到 guaranteed copy elision (new to C++17. See also ) 的精彩世界。
foo make_foo(int x) {
return foo(x);
}
int main() {
foo f = make_foo(1);
foo g(make_foo(2));
}
在所有这些情况下,您都是从 foo
类型的纯右值初始化 foo
,所以我们只是忽略所有中间对象并直接从实际初始化器初始化最外层的对象.这完全等同于:
foo f(1);
foo g(2);
我们在这里甚至不考虑移动构造函数 - 所以它们被删除的事实并不重要。具体规则是[dcl.init]/17.6.1 - 只有在这一点之后,我们才考虑构造函数并执行重载决议。
请注意,C++17 之前的版本(在保证复制省略之前),您可能已经 return 带有大括号初始化列表的对象:
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));
最近,我遇到了这个 std::array
非默认构造元素。我并不感到惊讶,因为这个答案显然没有进行任何默认构造。
相反,它使用聚合初始化构造一个临时 std::array
,然后在函数 return 时移动(如果移动构造函数可用)或复制到命名变量中。所以我们只需要移动构造函数或复制构造函数可用。
大概我是这么想的...
然后就出现了这段代码,把我搞糊涂了:
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));
}
所有五个特殊成员 constructors/operators 都被显式删除,所以现在我不应该能够从 return 值构造我的对象,对吗?
错了。
令我惊讶的是,这在 gcc 中编译(使用 C++17)!
为什么要编译?显然从函数make_foo()
到return一个foo
,我们要构造一个foo
。这意味着在 main()
函数中,我们正在从 returned foo
中分配或构造一个 foo
。这怎么可能?!
欢迎来到 guaranteed copy elision (new to C++17. See also
foo make_foo(int x) {
return foo(x);
}
int main() {
foo f = make_foo(1);
foo g(make_foo(2));
}
在所有这些情况下,您都是从 foo
类型的纯右值初始化 foo
,所以我们只是忽略所有中间对象并直接从实际初始化器初始化最外层的对象.这完全等同于:
foo f(1);
foo g(2);
我们在这里甚至不考虑移动构造函数 - 所以它们被删除的事实并不重要。具体规则是[dcl.init]/17.6.1 - 只有在这一点之后,我们才考虑构造函数并执行重载决议。
请注意,C++17 之前的版本(在保证复制省略之前),您可能已经 return 带有大括号初始化列表的对象:
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));