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

时间:2018-07-20 13:24:10

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

最近,我碰到了这个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。那怎么可能?!

2 个答案:

答案 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));