为什么当删除复制或移动构造函数时,命名返回值优化的 C++ 编译会失败?

Nat*_*mal 6 c++ return-value-optimization c++17 c++20

我在 C++ 11/17/20/23 上的 C++ 上的 gcc 13.1 上尝试了以下操作,但是当删除移动或复制构造函数时,它无法编译。

如果未删除这些构造函数,则命名返回值优化将起作用,并且复制/移动都不会完成。

有趣的是,如果我删除名称并直接返回纯右值,那么简单的返回值优化就会起作用。

谁能对此提供解释?

#include <memory>
#include <iostream>

struct Foo{
    Foo(int v): a{v} { std::cout << "Create!\n"; }
    ~Foo() { std::cout << "Destruct!\n"; }


    Foo(const Foo&)=delete;
    Foo(Foo&&)=delete;
    
    int a;
};

// I DON'T WORK!
Foo makeFoo() {
    Foo foo{5};
    return foo;
}

// I WORK!
//Foo makeFoo() {
//    return Foo{5};
//}

int main() {
    auto foo = makeFoo();
    std::cout << "Hello world! " << foo.a << "\n";
}
Run Code Online (Sandbox Code Playgroud)

Pau*_*ers 5

尽管从 C++17 开始复制省略确实是强制性的,但在从函数按值返回命名对象时仍然必须提供移动构造函数是有原因的。

这是因为可以编写无法使用 NVRO 的代码。这是一个简单的例子:

std::string foo (bool b)
{
    std::string s1 = "hello";
    std::string s2 = "world";
    return (b) ? s1 : s2;
}
Run Code Online (Sandbox Code Playgroud)

现在,NVRO 的工作方式是为要在调用站点返回的对象分配内存,然后new在 中构造对象时进行实质上的放置foo

但是对于上面的代码,编译器无法做到这一点(无论如何在一般情况下都不是),因为它可能返回两个可能的对象,并且它提前不知道会是哪一个。因此,它被迫根据传入的内容从s1or移动构造返回的字符串。s2b

现在您可能会争辩说,在编译器不会遇到此问题的代码中,NVRO 不应该需要可行的移动构造函数。但委员会显然认为,什么是允许的,什么是不允许的,那么就会太混乱,我同意他们的观点。让我们面对现实吧,对于编译器编写者来说,生活已经够艰难的了。