如何在 C++20 中强制执行复制省略?[复制]

How to enforce copy elision in C++20? [duplicate]

提问人:Anne Quinn 提问时间:9/4/2023 最后编辑:Jan SchultkeAnne Quinn 更新时间:9/4/2023 访问量:164

问:

C++17 承诺引入 Copy Elision 作为一项要求,所以我从 C++14 一直升级到 C++20。就是为了这个。(RVO 作为可选的行为改变优化...让我在脑海中运行程序时真的晕车了。我对这个版本的 C++ 非常陌生,但我想规定完全返回对象的行为;它是否调用它的 copy 方法和 temp 的析构函数。

Object f() {
    Object x;
    x.value = 10;
    x.str = "example function";
    return x;
}

我必须在此函数中执行(或更改)任何特殊操作,以确保返回的对象始终省略调用其复制或移动构造函数和析构函数?另外,如果编译器不能中止编译,有没有办法要求编译器中止编译?如果可以的话,我不想不小心给编译器选择的能力。f()x

C++ 可视化工作室 语言律师 C++20 返回值优化

评论

7赞 cigien 9/4/2023
您的代码片段需要 NRVO(命名返回值优化),这不能保证。您可以这样做(假设可以以这种方式创建),这将触发强制性 RVO。return Object{10, "example function"};Object
0赞 Anne Quinn 9/4/2023
@cigien - 我不知道这这么容易,这是一个公平的要求,谢谢!
2赞 Ted Lyngmo 9/4/2023
如果构造函数不是,你甚至可以做explicitreturn {10, "example function"};

答:

-3赞 ParkurMaster 9/4/2023 #1

在 C++17 及更高版本中,您提供的示例将按预期工作,因为 NRVO 保证具有相同类型的 prvalue。

评论

3赞 Nelfeal 9/4/2023
RVO 正是无法保证的。
1赞 Jan Schultke 9/4/2023 #2

可选 NRVO

您显示的示例是命名返回值优化 (NRVO) 的示例。在这种情况下,编译器可以执行复制省略,但不需要执行复制省略。这意味着可以调用 的复制/移动构造函数,但也可以省略Object

[...]在以下情况下允许称为复制省略(可以组合以消除多个副本):

  • 在具有类返回类型的函数中的语句中,当表达式是具有自动存储持续时间的非易失性对象的名称时,该对象的类型与函数返回类型相同(忽略 CV-Qualification),可以通过将对象直接构造到函数调用的返回对象中来省略复制/移动操作。return

- [类.copy.elision] p1、p1.1

C++17 和 C++20 在这方面具有相同的规则。

强制性 RVO

C++17 中实际变化的是,普通的返回值优化 (RVO) 成为强制性的。每当您获得 prvalue 时,它就会启动,例如:return

Object f() {
    return {10, "example function"};
}
// or in C++20, with designated initializers
Object f() {
    return {.value = 10, .str = "example function"};
}

在这两种情况下,需要 C++17 编译器来省略副本:

[...];该语句通过从操作数进行复制初始化来初始化(显式或隐式)函数调用的返回的引用或 PRE值结果对象。return

- [stmt.return] 第2页

这意味着就像您在通话站点上写字一样;对于复制初始化:return { ... };Object x = { ... };

如果初始值设定项表达式是 prvalue,并且源类型的 cv 非限定版本与目标的类是同一类,则使用初始值设定项表达式来初始化目标对象。

- [dcl.init.general] 第 16.6.1 页

此规则意味着直接在调用站点初始化。这两个规则结合在一起意味着返回值优化。{ ... }Object

1赞 Nelfeal 9/4/2023 #3

查看 cppreference 关于复制省略的页面。从 C++17 开始,有一个保证形式和一个非强制形式。从技术上讲,保证复制省略甚至不再被视为复制省略,它只是对 prvalues 规范的更改。

当使用相同类类型的 prvalue(忽略 cv-qualification)初始化对象(包括在 return 语句中)时,会发生“保证复制省略”。根据定义,prvalue 没有名称,因此,在 C++17 之前,在 return 语句的情况下,这是一种非强制性优化,称为未命名返回值优化 (URVO)。从 C++17 开始,它被语言的根本变化所取代:

PR值在需要时不会具体化,然后直接构造到其最终目的地的存储中。

在您的代码中,被命名,因此它不是 prvalue,因此不能保证其副本(或移动)被省略。尽管如此,我相信大多数现代编译器都会在这种情况下执行复制省略,除非您特别要求他们不要这样做(例如,对于 GCC 和 Clang)。它被称为命名返回值优化 (NRVO),它是可能改变可观察到的副作用的仅有的两种优化之一。请注意,NRVO 在常量表达式的上下文中是被禁止的。x-fno-elide-constructors

如果要保证不执行复制或移动,则需要改为处理 prvalue:

Object f() {
    int value = 10;
    std::string str = "example function";
    return Object(value, str); // this is a prvalue being returned
}

// simpler
Object f() {
    return Object(10, "example function");
}

// even simpler if Object's constructor is implicit
Object f() {
    return {10, "example function"};
}

评论

0赞 Mike Vine 9/4/2023
题外话:对于那些感兴趣的人,允许改变可观察到的副作用的另一种优化是 en.cppreference.com/w/cpp/language/new#Allocation