Visual Studio 如何在C++20中实现复制省略?[副本]

6qftjkof  于 2023-10-23  发布在  其他
关注(0)|答案(3)|浏览(148)

此问题已在此处有答案

How does guaranteed copy elision work?(2个答案)
上个月关门了。
C17承诺引入复制省略作为一个要求,所以我已经从C14一直升级到C20。就为了这个。(RVO作为一种可选的行为改变优化...当我在脑子里运行一个程序时,我真的会晕车。)我对这个版本的C很陌生,但我想规定完全返回对象的行为;无论它是否调用它的复制方法和临时的析构函数。

  1. Object f() {
  2. Object x;
  3. x.value = 10;
  4. x.str = "example function";
  5. return x;
  6. }

在这个f()函数中,我必须做(或修改)什么特殊的事情来确保返回的对象总是省略调用它的复制或移动构造函数和x的析构函数吗?还有,如果编译器不能中止编译,有没有办法让它中止编译?我不想偶然地给予编译器选择的能力,如果我能帮助它的话。

0kjbasz6

0kjbasz61#

可选NRVO

上面的例子是命名返回值优化(Named Return Value Optimization,NRVO)。在这种情况下,允许编译器执行复制省略,但不要求执行复制省略。这意味着Object * 的复制/移动构造函数可以被调用,但也可以被省略。
[...]在以下情况下允许使用称为“副本省略”的方法(可结合使用以消除多个副本):

  • 在具有类返回类型的函数中的return语句中,当表达式是具有自动存储持续时间[...]的非易失性对象的名称,并且具有与函数返回类型相同的类型(忽略cv限定)时,可以通过将对象直接构造到函数调用的返回对象中来省略复制/移动操作。
  • [class.copy.elision] p1,p1.1
    C17和C20在这方面有相同的规则。

强制RVO

在C++17中,实际上改变的是普通的返回值优化(RVO)成为强制性的。每当你return一个纯右值时,这个就会启动,比如:

  1. Object f() {
  2. return {10, "example function"};
  3. }
  4. // or in C++20, with designated initializers
  5. Object f() {
  6. return {.value = 10, .str = "example function"};
  7. }

在这两种情况下,需要C++17编译器来省略副本:
[...]; return语句通过从操作数进行复制初始化,将返回的引用或(显式或隐式)函数调用的纯右值结果对象复制。

  • [stmt.return] p2
    这意味着return { ... };的工作方式就像您在调用站点编写Object x = { ... };一样;对于复制初始化:
    如果初始化器表达式是纯右值,并且源类型的cv非限定版本与目标类型的类是同一个类,则初始化器表达式用于初始化目标对象。
  • [dcl.init.general]第16.6.1页
    此规则意味着{ ... }在调用站点直接调用Object。这两个规则的组合意味着返回值优化。
展开查看全部
yqyhoc1h

yqyhoc1h2#

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

aoyhnmkz

aoyhnmkz3#

查看cppreference's page on copy elision。从C17开始,有一个保证形式和一个非强制形式。从技术上讲,保证副本省略甚至不再被认为是副本省略,它只是prvalues规范的一个变化。
当初始化一个对象(包括在返回语句中)时,会发生“保证复制省略”,该对象具有相同类类型的纯右值(忽略cv限定)。根据定义,纯右值没有名称,因此,在C
17之前,在return语句的情况下,这是一种非强制性的优化,称为未命名返回值优化(URVO)。自C++17以来,它被语言的根本变化所取代:
纯右值直到需要时才被具体化,然后它被直接构造到其最终目的地的存储中。
在你的代码中,x被命名,所以它不是纯右值,因此它的拷贝(或移动)不能保证被省略。尽管如此,我相信大多数现代编译器都会在这种情况下执行复制省略,除非你特别要求他们不要这样做(例如GCC和Clang的-fno-elide-constructors)。它被称为命名返回值优化(NRVO),它是仅有的两种可能改变可观察到的副作用的优化之一。请注意,在常量表达式的上下文中禁止使用NRVO。
如果你想保证不执行复制或移动,那么你需要处理一个纯右值:

  1. Object f() {
  2. int value = 10;
  3. std::string str = "example function";
  4. return Object(value, str); // this is a prvalue being returned
  5. }
  6. // simpler
  7. Object f() {
  8. return Object(10, "example function");
  9. }
  10. // even simpler if Object's constructor is implicit
  11. Object f() {
  12. return {10, "example function"};
  13. }
展开查看全部

相关问题