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

Why does C++ compilation for named return value optimization fail when the copy or move constructor is deleted?

提问人:Nathan Doromal 提问时间:7/20/2023 最后编辑:DailyLearnerNathan Doromal 更新时间:7/21/2023 访问量:136

问:

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

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

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

谁能对此做出解释?

#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";
}
C++ 17 C++ 20 返回值优化

评论

5赞 Yksisarvinen 7/20/2023
自 C++17 以来,RVO 必须进行复制省略。这意味着编译器必须这样做,并且它永远不会调用任何复制/移动操作。然而,NRVO 仍然只是一个可选的优化,因此编译器必须确保你的变量可以有效地返回(通过复制/移动)——即使它最终完全优化了复制/移动。
2赞 Yksisarvinen 7/20/2023
这回答了你的问题吗?什么是复制省略和返回值优化?
0赞 BoP 7/20/2023
当优化是可选的时,即使未使用该选项,代码也必须有效。

答:

5赞 Paul Sanders 7/20/2023 #1

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

这是因为可以在无法编写 NVRO 的地方编写代码。下面是一个简单的示例:

std::string foo (bool b)
{
    std::string s1 = "hello";
    std::string s2 = "world";
    return (b) ? s1 : s2;
}

现在,NVRO 的工作原理是为要在调用站点返回的对象分配内存,然后在 中构造对象时执行放置。newfoo

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

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