STL向量:移动向量的所有元素

69

我有两个STL向量AB,我想清除A的所有元素并将B的所有元素移动到A中,然后清空B。简单来说,我想做到这一点:

std::vector<MyClass> A;
std::vector<MyClass> B;
....
A = B;
B.clear();

由于B可能很长,执行此操作需要k*O(N)的时间,其中k是常量,Nmax(size_of(A), size_of(B))。我想知道是否有更有效率的方法。我能想到的一件事是将AB定义为指针,然后在常数时间内复制指针并清除B

8个回答

131

使用C++11,只需简单地这样做:

A = std::move(B);

现在A包含了之前由B持有的元素,而B现在是空的。这样可以避免复制:内部表示被从B简单地移动到A,因此这是一个O(1)的解决方案。

至于C++03,在Prætorian所说的方式中,你可以交换向量。有一个std::swap函数的特化版本,它将std::vector作为其参数。这实际上交换了内部表示,因此您最终避免了创建它们所持有元素的副本。此函数也以O(1)复杂度工作。


3
此时B已经为空,因此B.clear()不会有任何作用。是的,你仍然可以使用这个vector,只不过它是一个空的vector。 - mfontanini
1
由于左侧向量中的所有现有元素必须在您实际移动另一个向量的数据之前被销毁,因此它与 *this 的大小成线性关系。 - mfontanini
32
FYI,B不是空的,它处于有效但未指定状态。根据标准,移动后B的大小不能保证为0。这就是为什么“swap()”仍然有用而“move”不是其替代品的原因:只要A是空的,“swap()”能够确保B现在也为空。 - void.pointer
3
在这种情况下,@void.pointer与cppreference的文档存在矛盾。std::vector的移动构造函数定义包括:“使用移动语义从其他容器构造容器。分配器通过从其他容器所属的分配器进行移动构造而获得。移动后,保证其他容器为空(empty())。” - sheeldotme
8
你错了。你链接的是移动构造函数文档。这个回答在使用移动赋值。 - void.pointer
显示剩余5条评论

20

如果您使用C++11编译器,您可以将B移动到A中。

A = std::move(B);

如果您正在使用较旧的编译器,只需交换这两个值

A.swap(B);

两种情况下,唯一的O(N)操作是清空A的内容。在第一种情况下,清除将在赋值本身时完成,而在第二种情况下,当B超出范围时(因为内容已经交换),清除会发生。


6

std::move很好用。以下是示例代码

    vector<int> v1 = {1,2,3,10,20,30,100,200,300,999};
    vector<int> v2;

    cout << "Size of v1 before move = " << v1.size() << endl;
    cout << "Capacity of v1 before move = " << v1.capacity() << endl;

    v2 = std::move(v1);

    cout << "Size of v2 after move = " << v2.size() << endl;
    cout << "Capacity of v2 after move = " << v2.capacity() << endl;

    cout << "Size of v1 after move = " << v1.size() << endl;
    cout << "Capacity of v1 after move = " << v1.capacity() << endl;

-----------Output-------------------------
Size of v1 before move = 10
Capacity of v1 before move = 10
Size of v2 after move = 10
Capacity of v2 after move = 10
Size of v1 after move = 0
Capacity of v1 after move = 0

5
我有两个 STL 向量 A 和 B,我想要清除 A 的所有元素,并将 B 的所有元素移动到 A 中,然后清空 B。
可以通过组合使用 swap 来完成。首先将 AB 分别和另一个向量交换。然后再将一个空的 std::vector<>B 交换或者调用 clear() 函数。不同之处在于,clear() 不会释放内存,仅销毁对象。
std::vector<int> a, b; // initialize them somehow
swap(a,b);

// clear b without releasing the memory:
std::size_t capacity = b.capacity();
b.clear();
assert(b.capacity()==capacity);

// or release the memory
std::vector<int>().swap(b);
assert(b.capacity()==0);

4

只需在向量上调用clear,将花费o(1)的时间,因为clear不会做任何事情, 如果您真的想在将B分配给A后清除它,可以执行以下操作

A.swap(B);
{
    std::Vector<..> C;
    c.swap(B);
}

2
如果您无法使用std::move或std::swap向量(例如,因为A和B是相关但不同的类型,可能仅由const不同),则可以执行以下操作:
std::vector<MyClass>       A;
std::vector<const MyClass> B;
// ...
for( auto& a : A )
{
    B.emplace_back( std::move( a ) );
}

请注意,这将使A拥有相同数量的元素,但它们都处于不确定状态(即,它们可以被赋值或销毁,但不能读取)。

2
交换函数执行此操作。
#include <iostream>
#include <iterator>
#include <vector>

int main(int argc, char* argv)
{
  std::vector<int> A;
  std::vector<int> B;

  for (int i = 0; i < 10; ++i)
  {
     B.push_back(i);
  }

  std::cout << "Before swap\n";
  std::cout << "A:";
  std::copy(A.begin(), A.end(), std::ostream_iterator<int>(std::cout, " "));
  std::cout << "\nB:";
  std::copy(B.begin(), B.end(), std::ostream_iterator<int>(std::cout, " "));
  std::cout << "\n";

  A.swap(B);
  B.clear();

  std::cout << "After swap\n";
  std::cout << "A:";
  std::copy(A.begin(), A.end(), std::ostream_iterator<int>(std::cout, " "));
  std::cout << "\nB:";
  std::copy(B.begin(), B.end(), std::ostream_iterator<int>(std::cout, " "));
  std::cout << "\n";
}

输出
Before swap
A:
B:0 1 2 3 4 5 6 7 8 9 
After swap
A:0 1 2 3 4 5 6 7 8 9 
B:

你不能使用 clear。函数 clear 只会移除元素,但为该向量分配的内存并不会被释放。 - BlackMamba

2
我没有足够的声望来评论,但我想提到根据:https://en.cppreference.com/w/cpp/container/vector/operator%3D,void.pointer是正确的。特别地,移动赋值运算符用移动语义替换内容(即将other中的数据从other移动到此容器中)。之后,other处于有效但未指定状态。
因此,Praetorian的答案在标准下是错误的。然而,对于至少MSVC而言,它已经足够好了,因为实现会清除列表(这对大多数情况也是真的)。
有趣的是,由于我们声明了移动构造函数,不会声明隐式移动赋值运算符。因此,我们“知道”std::vector必须声明一个移动赋值运算符。

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