如何保证副本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)。