如何保证副本elision工作?

在2016年的Oulu ISO C ++标准会议上,标准委员会将一个名为Guaranteed Copy Elision的提议通过简化的值类别投票给了C ++ 17。

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

复制elision被允许在一些情况下发生。 但是,即使允许,代码仍然必须能够工作,仿佛副本没有被消除。 也就是说,必须有一个可访问的副本和/或移动构造函数。

保证副本elision重新定义了一些C ++的概念,使某些复制/移动可能被消除的情况实际上不会引起任何复制/移动。 编译器不会删除副本; 该标准说,没有这样的复制可能会发生。

考虑这个function:

T Func() {return T();} 

在非保证的复制elision规则下,这将创build一个临时的,然后从临时移动到函数的返回值。 移动操作可能会被忽略,但即使从未使用, T仍然必须具有可访问的移动构造器。

同理:

 T t = Func(); 

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

保证副本elision 重新定义了prvalueexpression式的意义 。 Pre-C ++ 17,prvalues是临时对象。 在C ++ 17中,一个prvalueexpression式只是一个可以实现一个临时的东西,但它不是暂时的。

如果使用prvalue初始化prvaluetypes的对象,则不会实现临时性。 当你return T(); ,这通过prvalue初始化函数的返回值。 由于该函数返回T ,所以不会创build临时的; prvalue的初始化只是直接初始化返回值。

要理解的是,由于返回值是一个prvalue,它不是一个对象呢。 它只是一个对象的初始化器,就像T()一样。

当你做T t = Func(); ,返回值的前值直接初始化对象t ; 没有“创build临时和复制/移动”的阶段。 由于Func()的返回值是一个等价于T() ,因此t直接由T()初始化,就像完成了T t = T()

如果以任何其他方式使用prvalue,则prvalue将实现临时对象,该临时对象将在该expression式中使用(或者如果没有expression式,则被丢弃)。 所以,如果你没有const T &rt = Func(); ,这个值会临时实现(使用T()作为初始值),它的引用将被存储在rt ,以及通常的临时生命周期扩展。

保证elision允许你做的一件事是返回不动的对象。 例如, lock_guard不能被复制或移动,所以你不能有一个函数返回值。 但是,有保证的副本,你可以。

有保证的elision也适用于直接初始化:

 new T(FactoryFunction()); 

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

因此,通过值返回的工厂函数可以直接初始化堆分配的内存,甚至不知道它。 当然,只要这些function在内部遵循保证复制的规则。 他们必须返回一个typesT

当然,这也是有效的:

 new auto(FactoryFunction()); 

如果你不喜欢写types名称。


认识到上述保证只适用于价值是很重要的。 也就是说,当你返回一个命名variables时你不能保证:

 T Func() { T t = ...; ... return t; } 

在这种情况下, t必须仍然有一个可访问的复制/移动构造函数。 是的,编译器可以select优化复制/移动。 但编译器仍然必须validation是否存在可访问的复制/移动构造函数。

所以没有任何更改名为返回值优化(NRVO)。