保证拷贝省略是如何工作的? 2016届OULU ISOC++标准会议,标准委员会将提案称为C++ 17。

保证拷贝省略是如何工作的? 2016届OULU ISOC++标准会议,标准委员会将提案称为C++ 17。,c++,c++17,copy-elision,C++,C++17,Copy Elision,保证拷贝省略究竟是如何工作的?它是否涵盖了一些已经允许删除复制的情况,或者需要更改代码以保证删除复制?在许多情况下,删除复制是允许的。然而,即使它被允许,代码仍然必须能够工作,就好像副本没有被省略一样。也就是说,必须有一个可访问的拷贝和/或移动构造函数 保证拷贝删除重新定义了许多C++概念,使得某些复制或移动可以避免的情况实际上不会引起拷贝/移动。编译器并没有省略一个副本;该标准规定,此类复制不可能发生 考虑这一功能: T Func() {return T();} 在非保证复制省略规则下,这将

保证拷贝省略究竟是如何工作的?它是否涵盖了一些已经允许删除复制的情况,或者需要更改代码以保证删除复制?

在许多情况下,删除复制是允许的。然而,即使它被允许,代码仍然必须能够工作,就好像副本没有被省略一样。也就是说,必须有一个可访问的拷贝和/或移动构造函数

保证拷贝删除重新定义了许多C++概念,使得某些复制或移动可以避免的情况实际上不会引起拷贝/移动。编译器并没有省略一个副本;该标准规定,此类复制不可能发生

考虑这一功能:

T Func() {return T();}
在非保证复制省略规则下,这将创建一个临时,然后从该临时移动到函数的返回值中。该移动操作可以省略,但即使从未使用过,也必须具有可访问的移动构造函数

同样地:

T t = Func();
这是
t
的复制初始化。这将复制initialize
t
,返回值为
Func
。但是,
T
仍然必须有一个移动构造函数,即使它不会被调用

保证拷贝省略。在C++17之前,prvalues是临时对象。在C++17中,prvalue表达式仅仅是可以具体化临时值的东西,但它还不是临时值

如果使用prvalue初始化prvalue类型的对象,则不会具体化临时对象。当您执行
时返回T(),这将通过prvalue初始化函数的返回值。由于该函数返回
T
,因此不会创建临时值;prvalue的初始化只是直接初始化返回值

需要理解的是,由于返回值是prvalue,所以它还不是对象。它只是对象的初始值设定项,就像
T()
is一样

当您执行
T=Func()时
,返回值的prvalue直接初始化对象
t
;没有“创建临时和复制/移动”阶段。由于
Func()
的返回值是一个与
T()
等价的PR值,
T
T()
直接初始化,就像您执行了
T=T()
一样

如果以任何其他方式使用prvalue,prvalue将具体化一个临时对象,该对象将在该表达式中使用(如果没有表达式,则丢弃)。所以如果你做了
const T&rt=Func(),prvalue将具体化一个临时值(使用
T()
作为初始值设定项),其引用将与通常的临时生存期扩展内容一起存储在
rt

保证省略允许您做的一件事是返回固定的对象。例如,
lock\u guard
无法复制或移动,因此无法使用按值返回的函数。但有了保证的拷贝省略,您可以

保证省略也适用于直接初始化:

new T(FactoryFunction());
如果
FactoryFunction
按值返回
T
,则此表达式不会将返回值复制到分配的内存中。它将分配内存,并将分配的内存直接用作函数调用的返回值内存

因此,按值返回的工厂函数可以直接初始化堆分配的内存,而不需要知道它。当然,只要这些函数在内部遵循保证副本省略的规则。它们必须返回类型为
T
的PR值

当然,这也是可行的:

new auto(FactoryFunction());
以防你不喜欢打字


必须认识到,上述保证仅适用于PRV值。也就是说,当返回命名变量时,不能保证:

T Func()
{
   T t = ...;
   ...
   return t;
}
在本例中,
t
必须仍然具有可访问的复制/移动构造函数。是的,编译器可以选择优化复制/移动。但编译器仍必须验证是否存在可访问的复制/移动构造函数


因此,命名返回值优化(NRVO)没有任何变化。

我认为这里已经很好地分享了复制省略的细节。然而,我发现了这篇文章:它在返回值优化的情况下引用了C++17中的保证拷贝省略

它还提到了如何使用gcc选项:-fno elide构造函数,可以禁用复制省略,并看到不是在目的地直接调用构造函数,而是调用2个复制构造函数(或c++11中的move构造函数)及其相应的析构函数。以下示例显示了这两种情况:

#include <iostream>
using namespace std;
class Foo {
public:
    Foo() {cout << "Foo constructed" << endl; }
    Foo(const Foo& foo) {cout << "Foo copy constructed" << endl;}
    Foo(const Foo&& foo) {cout << "Foo move constructed" << endl;}
    ~Foo() {cout << "Foo destructed" << endl;}
};

Foo fReturnValueOptimization() {
    cout << "Running: fReturnValueOptimization" << endl;
    return Foo();
}

Foo fNamedReturnValueOptimization() {
    cout << "Running: fNamedReturnValueOptimization" << endl;
    Foo foo;
    return foo;
}

int main() {
    Foo foo1 = fReturnValueOptimization();
    Foo foo2 = fNamedReturnValueOptimization();
}
vinegupt@bhoscl88-04(~/progs/cc/src)$ g++ -std=c++11 testFooCopyElision.cxx # Copy elision enabled by default
vinegupt@bhoscl88-04(~/progs/cc/src)$ ./a.out
Running: fReturnValueOptimization
Foo constructed
Running: fNamedReturnValueOptimization
Foo constructed
Foo destructed
Foo destructed
vinegupt@bhoscl88-04(~/progs/cc/src)$ g++ -std=c++11 -fno-elide-constructors testFooCopyElision.cxx # Copy elision disabled
vinegupt@bhoscl88-04(~/progs/cc/src)$ ./a.out
Running: fReturnValueOptimization
Foo constructed
Foo move constructed
Foo destructed
Foo move constructed
Foo destructed
Running: fNamedReturnValueOptimization
Foo constructed
Foo move constructed
Foo destructed
Foo move constructed
Foo destructed
Foo destructed
Foo destructed
#包括
使用名称空间std;
福班{
公众:

Foo(){cout@BenVoigt:将非平凡的可复制用户定义类型放入寄存器不是ABI可以做的事情,不管省略是否可用。现在规则是公开的,可能值得用“prvalues are initializations”概念来更新它。@JohannesSchaub litb:这只是“不明确的”如果你对C++标准的细节有太多的了解,对于C++社区的99%,我们知道“保证拷贝删除”指什么。实际提出该特征的论文甚至被称为“保证拷贝删除”。这只会让用户感到困惑和难以理解。这也是一个用词不当的说法,因为这些规则并没有真正“简化”围绕价值类别的规则。不管你喜欢与否,术语“保证副本省略”指的是此功能,而不是其他功能。我希望能够提取PR值并随身携带。我想这只是一个(一次性)
std::function
真的。@LukasSalich:这是一个C++11问题。这个答案是关于C++17功能的。