代码之家  ›  专栏  ›  技术社区  ›  Bernard

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

  •  19
  • Bernard  · 技术社区  · 6 年前

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

    相反,它正在构建一个临时的 STD::阵列 使用聚合初始化,然后在函数返回时移动(如果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() ,我们必须构建一个 . 这意味着在 main() 我们正在分配或构造的函数 从返回的 . 这怎么可能?!

    2 回复  |  直到 6 年前
        1
  •  29
  •   Barry    6 年前

    欢迎来到美好的世界 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 f(1);
    foo g(2);
    

    我们甚至不考虑在这里移动构造函数——所以删除它们并不重要。具体规则是 [dcl.init]/17.6.1 -只是 之后 这一点我们考虑了构造函数并执行重载解析。

        2
  •  0
  •   Jarod42    6 年前

    注意,pre-c++17(在保证删除拷贝之前),您可能已经用有支撑的init列表返回了该对象:

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