c++ - 保证复制省略如何工作?

标签 c++ c++17 copy-elision

在 2016 年奥卢 ISO C++ 标准 session 上,一项名为 Guaranteed copy elision through simplified value categories 的提案被标准委员会投票为 C++17。

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

最佳答案

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

保证复制省略重新定义了许多 C++ 概念,因此可以省略复制/移动的某些情况实际上根本不会引发复制/移动。编译器不会删除拷贝;标准说这种复制永远不会发生。

考虑这个函数:

T Func() {return T();}

在非保证复制省略规则下,这将创建一个临时文件,然后从该临时文件移入函数的返回值。该移动操作可能会被省略,但 T即使从未使用过,也必须具有可访问的移动构造函数。

同样:
T t = Func();

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

保证复制省略 redefines the meaning of a prvalue expression .在 C++17 之前,纯右值是临时对象。在 C++17 中,纯右值表达式只是可以具体化临时对象的东西,但它还不是临时对象。

如果您使用纯右值来初始化纯右值类型的对象,则不会具体化临时对象。当您这样做时return T(); ,这通过纯右值初始化函数的返回值。由于该函数返回 T , 没有临时创建;纯右值的初始化只是直接初始化返回值。

需要理解的是,由于返回值是一个纯右值,所以它还不是一个对象。它只是一个对象的初始化器,就像 T()是。

当您这样做时T t = Func(); ,返回值的纯右值直接初始化对象t ;没有“创建临时和复制/移动”阶段。自 Func()的返回值是一个等价于 T() 的纯右值, tT() 直接初始化,就好像你已经做了 T t = T() .

如果以任何其他方式使用纯右值,则纯右值将具体化一个临时对象,该对象将在该表达式中使用(如果没有表达式,则将其丢弃)。所以如果你做了 const T &rt = Func(); ,纯右值将实现一个临时的(使用 T() 作为初始化器),其引用将存储在 rt 中。 ,以及通常的临时生命周期延长的东西。

保证省略允许您做的一件事是返回不可移动的对象。例如,lock_guard不能被复制或移动,所以你不能有一个按值返回它的函数。但是通过保证复制省略,您可以。

保证省略也适用于直接初始化:
new T(FactoryFunction());

FactoryFunction返回 T按值,此表达式不会将返回值复制到分配的内存中。它将改为分配内存并将分配的内存直接用作函数调用的返回值内存。

所以按值返回的工厂函数可以直接初始化堆分配的内存,甚至不知道它。当然,只要这些函数在内部遵循保证复制省略的规则。他们必须返回类型为 T 的纯右值.

当然,这也有效:
new auto(FactoryFunction());

如果你不喜欢写类型名。

重要的是要认识到上述保证仅适用于纯右值。也就是说,您在返回命名变量时得不到任何保证:
T Func()
{
   T t = ...;
   ...
   return t;
}

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

所以命名返回值优化(NRVO)没有任何变化。

关于c++ - 保证复制省略如何工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38043319/

相关文章:

c++ - RVO 和复制省略是否只能在一个编译单元内工作?

c# - 排序 C++ vector 和 C# 列表

c++ - grpc c++ 异步完成队列事件

c++ - 如何有效地获得给定范围内的除数之和?

c++ - 是否可以在 constexpr 函数中遍历枚举成员,因此值是 constexpr?

c++ - 折叠表达式 vs 编译递归

c++ - <regex> std::regex 相当于 Qt 的 QRegularExpression::isValid() 且不会触发异常

c++ - 消除函数参数的复制

c++ - 'using' 关键字与多个重载