删除了所有自动生成的构造函数/运算符的类仍然可以从函数返回?

Ber*_*ard 19 c++ return-value deleted-functions c++17

最近,我遇到了这个答案,它描述了如何初始化一个std::array非默认可构造元素.我并不感到惊讶,因为这个答案显然没有做任何默认构建.

相反,它std::array使用聚合初始化构建临时,然后移动(如果移动构造函数可用)或在函数返回时复制到命名变量.所以我们只需要移动构造函数或复制构造函数即可.

或者我认为......

然后是这段代码让我感到困惑:

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));
}
Run Code Online (Sandbox Code Playgroud)

所有五个特殊成员构造函数/运算符都被显式删除,所以现在我不能从返回值构造我的对象,对吗?

错误.

令我惊讶的是,这在gcc中编译(使用C++ 17)!

为什么编译?显然foo要从函数返回一个make_foo(),我们必须构造一个foo.这意味着在main()我们foo从返回的函数中分配或构造一个函数foo.怎么可能?!

Bar*_*rry 29

欢迎来到保证副本省略的精彩世界(C++新手17.请参阅此问题).

foo make_foo(int x) {
    return foo(x);
}

int main() {
    foo f = make_foo(1);
    foo g(make_foo(2));
}
Run Code Online (Sandbox Code Playgroud)

在所有这些情况下,您正在foo从类型的prvalue 初始化a foo,因此我们只是忽略所有中间对象并直接从实际初始化器初始化最外层对象.这完全等同于:

foo f(1);
foo g(2);
Run Code Online (Sandbox Code Playgroud)

我们甚至不考虑移动构造函数 - 因此它们被删除的事实并不重要.具体规则是[dcl.init] /17.6.1 - 只有这一点之后我们才会考虑构造函数并执行重载决策.