为什么g++这里没有启用RVO?

发布于 2025-01-06 13:45:27 字数 702 浏览 1 评论 0原文

考虑一下测试代码:

#include <iostream>
using namespace std;

class Klass
{
public:
  Klass()
  {
    cout << "Klass()" << endl;
  }

  Klass(const Klass& right)
  {
    cout << "Klass(const Klass& right)" << endl;
  }
};

Klass create(Klass a)
{
  cout << "create(Klass a)" << endl;
  return a;
}

int main()
{
  const Klass result = create(Klass());
}

编译为:

g++ -O3 rvo.cpp   -o rvo

输出为:

$ ./rvo
Klass()
create(Klass a)
Klass(const Klass& right)

我期望编译器使用 RVO 机制来消除每个 COPY CTOR 调用,以避免复制函数 create( 的返回值和参数) )。为什么事实并非如此?

Consider that TEST code:

#include <iostream>
using namespace std;

class Klass
{
public:
  Klass()
  {
    cout << "Klass()" << endl;
  }

  Klass(const Klass& right)
  {
    cout << "Klass(const Klass& right)" << endl;
  }
};

Klass create(Klass a)
{
  cout << "create(Klass a)" << endl;
  return a;
}

int main()
{
  const Klass result = create(Klass());
}

Compiling with:

g++ -O3 rvo.cpp   -o rvo

The output is:

$ ./rvo
Klass()
create(Klass a)
Klass(const Klass& right)

I was expecting the compiler to use the RVO mechanism in order elide every COPY CTOR call, to avoid copying the return value AND the parameter of the function create(). Why isn't it the case?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

心病无药医 2025-01-13 13:45:27

仅当您将临时参数作为函数参数传递时,该标准才允许复制省略。

您期望的两个省略在下面以粗体显示:

[C++11: 12.8/31]: 当满足某些条件时,允许实现省略类对象的复制/移动构造,即使复制/移动构造函数和/或对象的析构函数有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象本应被删除的时间的较晚时间。未经优化就被破坏。 这种复制/移动操作的省略称为复制省略,在以下情况下是允许的(可以组合起来消除多个副本):

  • 在具有类返回类型的函数的 return 语句中,当表达式是非易失性自动对象的名称时(除了函数或 catch 子句参数)与函数返回类型相同的cv非限定类型,可以通过将自动对象直接构造到函数的返回值中来省略复制/移动操作
  • 在 throw 表达式中,当操作数是非易失性自动对象(函数或 catch 子句参数除外)的名称时,其范围不会超出最内层封闭 try 块的末尾(如果有一个),通过将自动对象直接构造到异常对象中,可以省略从操作数到异常对象(15.1)的复制/移动操作
  • 当尚未绑定到引用 (12.2) 的临时类对象被复制/移动到具有相同 cv-unqualified 类型的类对象时,可以通过构造临时对象直接进入省略复制/移动的目标
  • 当异常处理程序的异常声明(第 15 条)声明与异常对象(15.1)相同类型(cv 限定除外)的对象时,可以通过处理异常来省略复制/移动操作- 声明作为异常对象的别名,如果除了执行由异常声明声明的对象的构造函数和析构函数之外,程序的含义将保持不变。 [..]

返回值没有发生这种情况,因为非易失性名称是函数参数。

已经发生在create参数的构造中,否则你会看到:

Klass()
Klass(const Klass& right)
create(Klass a)
Klass(const Klass& right)

The standard allows copy elision only in case where you pass a temporary as a function argument.

The two elisions you're expecting are bolded below:

[C++11: 12.8/31]: When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization. This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

  • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value
  • in a throw-expression, when the operand is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object (15.1) can be omitted by constructing the automatic object directly into the exception object
  • when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
  • when the exception-declaration of an exception handler (Clause 15) declares an object of the same type (except for cv-qualification) as the exception object (15.1), the copy/move operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration. [..]

It didn't happen for the return value because the non-volatile name was a function parameter.

It has happened for the construction into create's parameter, otherwise you'd have seen:

Klass()
Klass(const Klass& right)
create(Klass a)
Klass(const Klass& right)
羁拥 2025-01-13 13:45:27

您看到的副本是“create”函数中“return”语句的副本。 RVO 无法消除它,因为无法直接构造返回值。您请求“返回”。这里需要一份副本;没有它就无法返回对象。

用标准的话说,不满足 [C++11: 12.8/31] 的以下条件

在具有类返回类型的函数的 return 语句中,当表达式是非易失性自动对象的名称(除了function 或 catch-clause parameter) 与函数返回类型具有相同的 cv-unqualified 类型,可以通过将自动对象直接构造到函数的返回值中来省略复制/移动操作

。原因,这不是一个任意的规则,它是有道理的从实现的角度来看,因为这是函数参数无法做到的:

直接将自动对象构造到函数的返回值中

您正在复制函数参数。您无法在不内联的情况下删除此副本,因为参数在进入函数之前已经存在,因此您不能直接将该对象构造到返回值中。

The copy you see is a copy for the "return" statement in the "create" function. It cannot be eliminated by RVO, as it is not possible to construct the return value directly. You requested to "return a". A copy is needed here; there is no way to return an object without it.

In a standard speak, following condition of [C++11: 12.8/31] is not met

in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

As for the reasons, it is not an arbitrary rule, it makes sense from implementation point of view, as this is what is not possible to do with a function parameters:

constructing the automatic object directly into the function’s return value

You are copying the function parameter. You cannot elide this copy without inlining, as the parameter already exists before you enter the function, therefore you cannot construct that object into the return value directly instead.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文