std::vector.push_back(std::move(foo)) 有意义吗?

8

我在一些代码中遇到了这个问题(为了清晰起见,细节已被省略):

std::vector<std::vector<int>> foo;
{
    std::vector<int> bar = {42};
    foo.push_back(std::move(bar)); // Hmmm...
} // Indicate `bar` is no longer needed.
std::move 对我来说似乎是不必要的,但它真的吗?与仅使用 foo.push_back(bar); 相比,行为是否有任何不同?如果元素不是 int 而是一个类,例如在我的实际代码中一样是 pcl::PointXYZ,会发生什么情况? 更新:我已经修改了代码,更明确地表明在使用 std::move 后不再使用 bar,因此没有非法访问等风险。
2个回答

11

vector有两个push_back实现:

void push_back( const T& value );
void push_back( T&& value );

第一个函数对给定的元素进行复制。

第二个函数尝试通过调用元素的移动构造函数(如果定义了)来“移动”它。

使用 move 强制选择第二种实现,这种实现应该是重用值而不只是复制它。

在这个特定的例子中,将会发生以下情况:

  1. 向量 bar 在堆栈上分配,但是它的元素(42)在堆上分配。
  2. 当你调用 foo.push_back(...) 时,foo 在堆上分配了一个新的向量,这将是 bar 的副本。让我们称之为 baz :) 根据调用哪种 push_back 实现,然后将发生以下情况:
    • void push_back( const T& value );:在这种情况下,所有 bar 的元素也将被复制到 baz 中。
    • void push_back( T&& value );:在这种情况下,baz 将接收指向 bar 元素的指针,因此不执行任何复制操作。但是关键是要理解,bar 将被剥夺其元素(现在 baz 拥有它们),因此在 move 之后不应再使用 bar

元素的类型并不重要(是普通的整数还是 pcl::PointXYZ),因为仅有第一个向量分配了元素的内存,并且该内存的指针是在 move 调用期间复制的唯一内容。


所以答案是肯定的,它是必要的(对于“必要”的适当值),因为行为不同,我们应该保存包含“42”的内存副本。然而,我可以说一个聪明的编译器可以并且可能会找出优化,但std::move使其明确优化必须发生吗? - Ken Y-N
2
@KenY-N,是的。不仅使它明确,而且还使它工作。(没有std::move,“优化”将不会发生)。您甚至可以使用emplacefoo.emplace(std::move(bar));。如果代码如此简单(在push_back之前没有使用var),则可以使用foo.emplace(std :: vector {42}),甚至可以使用foo.emplace({42}) - alfC
1
@alfC 我想,在大多数情况下,使用 var 可能会被推迟到 emplace 之后,因为 emplace 旨在返回迭代器? - Swift - Friday Pie

1

我觉得 std::move 看起来有点多余,但是需要使用吗?

这取决于你的意图。

与 foo.push_back(bar); 相比,行为有什么不同吗?

是的,foo.push_back(bar); 会将 bar 复制到 foo 中(可能会带来性能损失,因为 std::vector 处理动态分配)。这也会使 bar 保持不变,之后还可以使用它。

另一方面,foo.push_back(std::move(bar)); 不会复制任何内容,并重用已经分配的内存中的 bar。请注意,在移动后,bar 的状态是有效但未指定的(也就是说,除非重新初始化/重新赋值,否则无法使用它)。

如果元素是一个类,例如在我的实际代码中的 pcl::PointXYZ,会发生什么?

移动语义仅适用于使用动态分配(拥有指针)的类类型。pcl::PointXYZ和int不是这样的类,因此对int或pcl::PointXYZ进行std::move操作没有意义。

注意,我正在移动的实际上是一个 std::vectorint - Ken Y-N
@KenY-N 这并不重要。内存仍然会为int分配。在这方面,移动一个std::vector<int>是有意义的。 - DeiDei

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接