首页 > 解决方案 > 删除了所有自动生成的构造函数/运算符的类仍然可以从函数返回吗?

问题描述

最近,我遇到了这个答案,它描述了如何初始化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));
}

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

错误的。

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

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

标签: c++return-valuec++17deleted-functions

解决方案


欢迎来到保证复制省略的美妙世界(C++17 的新手。另请参阅此问题)。

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

int main() {
    foo f = make_foo(1);
    foo g(make_foo(2));
}

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

foo f(1);
foo g(2);

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


推荐阅读