提问人:fab 提问时间:3/15/2023 最后编辑:fab 更新时间:3/16/2023 访问量:256
复制/移动省略与使用 std::optional 进行放置
Copy/move elision vs emplace with std::optional
问:
我使用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
答:
std::optional<T>::emplace(x, y, z)
保证它将 、 转发给构造对象的构造函数,并且不对构造对象进行任何复制或移动。x
y
z
T
T
std::optional<T>::emplace({...})
其中是一个可以转换为 will not 编译的 designated-initializer-list,所以当你声称它会导致相同的程序集时,我不确定你的意思。请参见 Godbolt。在任何情况下,designated-initializer-lists 都无法完全转发,因此即使这有效,您也不能指望省略是可能的。{...}
T
在另一种替代方法中,将对象传递给函数,该函数将通过引用获取其参数(请参阅 cppreference 上的构造函数 4)。绑定引用会强制实现临时的 ,然后必须将其移动到存储的对象中。绑定到引用的临时不能省略。此外,如果未创建临时对象,则不会有 RHS 用于从中分配存储对象。point_t
operator=
point_t
T
T
T
但是,当我们说省略不会发生时,它只是意味着,根据标准,程序必须表现得好像创建了临时对象,然后复制/移动了临时对象。如果是微不足道的可复制的(我猜你是),那么就不可能观察到是否有任何临时被复制/移动到存储对象中。每当优化的效果不可观察时,编译器总是被允许执行这样的优化。但是,当然,标准不能保证。同样,即使标准保证没有复制/移动,你也不能保证一个蹩脚的编译器不会生成效率极低的汇编代码,这些汇编代码在将值写入对象之前在各种寄存器之间毫无意义地打乱。T
Point_t
T
T
评论
std::optional
=
emplace
emplace
std::optional