在2016年Oulu ISO C ++标准会议上,标准委员会将名为“通过简化的值类别进行保证的复制省略”的提案投票选为C ++ 17。
保证的复制清除功能如何工作?它是否涵盖了某些已经允许使用复制消除的情况,还是需要更改代码以保证复制消除?
Answers:
在许多情况下都允许进行复制省略。但是,即使允许,该代码仍然必须能够像不删除副本一样工作。即,必须有一个可访问的副本和/或移动构造函数。
保证复制省略重新定义了一些C ++的概念,例如,某些情况下在那里拷贝/移动可能被省略不竟招来复制/移动所有。编译器没有保留副本;该标准说,永远不会发生这种复制。
考虑以下功能:
T Func() {return T();}
在无保证的复制省略规则下,这将创建一个临时变量,然后从该临时变量移至函数的返回值。此举操作可以被省略,但T
仍必须具有即使从未使用过它的访问移动构造函数。
类似地:
T t = Func();
这是的副本初始化t
。这将复制初始化t
,返回值为Func
。但是,T
即使它不会被调用,仍然必须具有move构造函数。
保证复制省略重新定义了一个prvalue表达式的含义。在C ++ 17之前的版本中,prvalue是临时对象。在C ++ 17中,prvalue表达式只是可以实现临时的东西,但还不是临时的。
如果使用prvalue初始化prvalue类型的对象,则不会实现任何临时对象。当您这样做时return T();
,这将通过prvalue初始化函数的返回值。由于该函数返回T
,因此不会创建任何临时函数;prvalue的初始化只是直接初始化返回值。
需要了解的是,由于返回值是prvalue,因此它还不是对象。就像T()
是一样,它只是对象的初始化程序。
当您这样做时T t = Func();
,返回值的prvalue直接初始化该对象t
;没有“创建临时和复制/移动”阶段。由于Func()
返回值是prvalue的等效值T()
,t
因此直接由初始化T()
,就像您已经做过一样T t = T()
。
如果以任何其他方式使用prvalue,则prvalue将实现一个临时对象,该临时对象将在该表达式中使用(如果没有表达式,则将其丢弃)。因此,如果您这样做了const T &rt = Func();
,则prvalue将实现一个临时(T()
用作初始化程序),其引用将rt
与通常的临时生命周期扩展内容一起存储在中。
确保省略允许您做的一件事是返回固定的对象。例如,lock_guard
不能被复制或移动,因此您不能具有按值返回它的函数。但只要保证复制省略,您就可以。
保证省略也可用于直接初始化:
new T(FactoryFunction());
如果按值FactoryFunction
返回T
,则此表达式不会将返回值复制到分配的内存中。相反,它将分配内存,并将分配的内存直接用作函数调用的返回值内存。
因此,按值返回的工厂函数可以直接初始化堆分配的内存,而无需了解它。当然,只要这些功能在内部遵循保证复制保留的规则即可。他们必须返回类型为的prvalue T
。
当然,这也可以:
new auto(FactoryFunction());
如果您不喜欢写类型名称。
重要的是要认识到上述保证仅适用于prvalue。也就是说,返回命名变量时无法保证:
T Func()
{
T t = ...;
...
return t;
}
在这种情况下,t
必须仍然具有可访问的复制/移动构造函数。是的,编译器可以选择优化复制/移动。但是编译器仍必须验证是否存在可访问的复制/移动构造函数。
因此,命名返回值优化(NRVO)不会发生任何变化。
std::function<T()>
。