RVO/NRVO启用时,对象是否被复制?

5
由于有多个问题像这样的问题,我无法理解RVO(和NRVO)的定义,因为它们似乎假设RVO省略了一个复制构造函数。根据12.8.15:
在这种情况下,实现将省略的复制操作的源和目标视为仅是引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象将在没有优化的情况下被销毁的时间中较晚的时间。
这看起来不是省略了复制构造函数调用,而是省略了复制本身——只是在第一次在“复制”位置构造对象,因此没有“原始”对象,也没有任何复制。因此,即使类具有私有复制构造函数,当RVO启动时,也可以从函数返回它,因为没有复制。
我理解得对吗?复制本身是否被省略?还是省略了复制构造函数的调用?如果对象类具有私有复制构造函数,是否应允许从函数返回对象?
6个回答

8

如果进行了优化,复制将被省略,但编译器仍然需要检查复制构造函数是否可访问。否则,如果编译器(或其他编译器)决定不进行优化,则代码将无效。


7

复制本身被省略了还是拷贝构造函数被省略了?

复制操作本身被省略了。如果你仔细看完全引用,它明确提到了这一点:

C++03 12.8类对象的复制

第15段

当符合特定条件时,允许实现省略类对象的复制构造,即使该对象的复制构造函数和/或析构函数具有副作用。在这种情况下,实现将省略复制操作的源和目标视为只是指向同一对象的两种不同方式,并且该对象的销毁发生在两个对象应在没有优化的情况下被销毁的时间中较晚的时间111)。可以在以下情况下允许省略复制操作(这些情况可以组合以消除多个复制):

—在带有类返回类型的函数的返回语句中,当表达式是具有与函数返回类型相同的cv非限定类型的非易失自动对象名称时,可以通过直接将自动对象构造到函数的返回值中来省略复制操作。

—当未将未绑定到引用(12.2)的临时类对象复制到具有相同cv非限定类型的类对象时,可以通过直接将临时对象构造到省略复制的目标中来省略复制操作.....

如果对象类有私有拷贝构造函数,是否允许从函数返回对象?

RVO和NRVO是编译器优化,由编译器允许但不保证。如果某个愚蠢的编译器无法提供这些优化,会发生什么呢?
没有可访问的拷贝构造函数,代码将在所有这样的编译器上崩溃,这是不期望的。因此应该使拷贝构造函数可访问。


3

官方上说,RVO/NRVO并不影响程序是否良好形式。标准明确允许省略复制构造函数的任何副作用,但是复制构造函数的访问检查仍然应该被执行。

然而,大多数编译器在省略复制构造函数时不会执行访问检查,除非你要求尽可能严格地执行规则。我不确定,但我记得曾经使用过一些即使你要求严格遵守,它们也会跳过访问检查(但我不确定)。

编辑(为了更正我认为其他答案中表达的误解):

标准中某个部分的段落是按顺序阅读的。适用于某种情况的第一个段落中的要求总是适用的。在这种情况下,可以省略副作用的许可在第15段落中。然而,在此之前的第14段落中,我们看到:

如果使用对象的复制构造函数或复制赋值运算符且特殊成员函数不可访问(第11条款),则程序是不良形式的。

因此,只有在通过第14段指定的访问检查后,才能进入第15段(其中可以省略复制构造)。


2

被省略的是拷贝本身。每当执行复制操作时,必须使用复制构造函数,但有几种情况下编译器允许优化掉一个拷贝。

较大和非POD对象通过堆栈返回。调用者为该对象准备空间,并将指向该空间的指针作为隐藏参数传递。然后,被调用方将对象复制到该空间中。这里可以进行两个优化:

  1. 被调用方可以直接在返回空间中创建对象。编译器必须确保该对象将被返回,即只能跟随返回该对象的语句,且中间代码不能抛出异常。
  2. 如果结果被赋值给变量,则调用者可以传递其地址而不是创建临时变量。只有在变量被初始化或效果可证明不会与调用operator=有所不同的情况下才能这样做,这基本上是指如果类型具有默认构造函数、析构函数和operator=

1
复制本身被省略了还是复制构造函数被省略了?
思路是省略了复制构造函数,尽管编译器允许共享内存,如果不改变程序的语义。
当对象类有一个私有复制构造函数时,从函数返回对象是否应该被允许?
不,这是不允许的。“应该”很难说,但允许这样做将允许您做各种花哨的事情并破坏封装性。在C++11中,您可以通过在返回中使用{}构造并移动构造该值来实现这一点。

-1:具有误导性。被省略的是拷贝。必须始终使用拷贝构造函数进行拷贝,否则一切都会乱套。所谓“应该”,很容易说,不,不应该,因为编译器不必优化掉拷贝,所以必须能够执行拷贝。 - Jan Hudec

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接