复制/移动省略与使用 std::optional 进行放置

Copy/move elision vs emplace with std::optional

提问人:fab 提问时间:3/15/2023 最后编辑:fab 更新时间:3/16/2023 访问量:256

问:

我使用std::optional来存储结构,我最初使用:

std::optional<Point_t> optPosition; // declared somewhere as a class member
optPosition.emplace(..., ..., ...);

效果很好,但我不太喜欢您必须在 Point_t 结构中显式定义构造函数,而且它的可读性也不是很强(想象一下如果我有更多的成员)。

所以我尝试了一些替代方案:

optPosition.emplace(point_t {
  .x = ...,
  .y = ...,
  .z = ...
});

optPosition = point_t {
  .x = ...,
  .y = ...,
  .z = ...
};

我担心我会受到一些复制/移动开销的影响,但 GCC 躲避了它们,并且生成的程序集在所有 3 种情况下都是相同的 (GCC11)。

我的问题是:
这是标准保证的,还是 GCC 很好?
如果保证,那么在什么情况下有用?
一般来说,除了检查asm代码外,我怎么知道不会添加任何复制/移动,什么时候我应该选择第一种情况(不太可读的地方)?
emplace

C++ gcc copy-elision emplace std可选

评论

0赞 Jesper Juhl 3/15/2023
只要编译器生成“足够快”的优化代码,你为什么真的在乎呢?
0赞 Drew Dormann 3/15/2023
当 a 已经有一个值时,是分配请求,而是销毁请求,然后是构造。std::optional=emplace
0赞 Davis Herring 3/16/2023
有趣的情况是,当参数 to 具有 的 包含类型的转换函数时。emplacestd::optional

答:

4赞 Brian Bi 3/15/2023 #1

std::optional<T>::emplace(x, y, z)保证它将 、 转发给构造对象的构造函数,并且不对构造对象进行任何复制或移动。xyzTT

std::optional<T>::emplace({...})其中是一个可以转换为 will not 编译的 designated-initializer-list,所以当你声称它会导致相同的程序集时,我不确定你的意思。请参见 Godbolt。在任何情况下,designated-initializer-lists 都无法完全转发,因此即使这有效,您也不能指望省略是可能的。{...}T

在另一种替代方法中,将对象传递给函数,该函数将通过引用获取其参数(请参阅 cppreference 上的构造函数 4)。绑定引用会强制实现临时的 ,然后必须将其移动到存储的对象中。绑定到引用的临时不能省略。此外,如果未创建临时对象,则不会有 RHS 用于从中分配存储对象。point_toperator=point_tTTT

但是,当我们说省略不会发生时,它只是意味着,根据标准,程序必须表现得好像创建了临时对象,然后复制/移动了临时对象。如果是微不足道的可复制的(我猜你是),那么就不可能观察到是否有任何临时被复制/移动到存储对象中。每当优化的效果不可观察时,编译器总是被允许执行这样的优化。但是,当然,标准不能保证。同样,即使标准保证没有复制/移动,你也不能保证一个蹩脚的编译器不会生成效率极低的汇编代码,这些汇编代码在将值写入对象之前在各种寄存器之间毫无意义地打乱。TPoint_tTT

评论

0赞 fab 3/16/2023
在第二种情况下,我忘记在{}之前包含一个“point_t”,这可能会使emplace调用Point_t的复制/移动构造函数(这确实是一个微不足道的结构)。感谢您的解释,我知道当类型不容易复制时,emplace 很有用;我将在这里保留第三个选项,我觉得它更具可读性。