C++:高效复制容器

4

您如何复制STL容器?

// big containers of POD
container_type<pod_type> source;
container_type<pod_type> destination

// case 1
destination = source;

// case 2
destination.assign(source.begin(), source.end());

// case 3 assumes that destination.size() >= source.size()
copy(source.begin(), source.end(), destination.size());

我尽可能使用情况1。容器类型不同时,我使用情况2。当目标大于源且要保留其余元素时,需要情况3。
但是对于非POD元素和具有非零构造/析构成本的元素呢?情况3是否比情况2更好?如果目标大于源,则实现可能会执行相当意外的操作。这是Visual Studio 2008在情况2中所做的事情。
  1. 销毁目标的所有元素。
  2. 然后调用复制构造函数与目标大小相同次数。为什么?
  3. 将源的所有元素分配给对应的目标元素。
  4. 销毁目标的额外元素。
GCC 4.5做得更好。所有源元素都通过赋值进行复制,然后销毁目标的额外元素。在两个平台上都可以使用情况3,然后使用resize来完成相同的操作(除了一个默认构造函数,resize需要此构造函数)。以下是展示我的意思的玩具程序。
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
using namespace std;

struct A {
    A() { cout << "A()\n"; }
    A(const A&) { cout << "A(const A&)\n"; }
    A& operator=(const A&) {
        cout << "operator=\n";
        return *this;
    }
    ~A() { cout << "~A()\n"; }
};

int main() {
    list<A> source(2);
    vector<A> desrination1(3);
    vector<A> desrination2(3);

    cout << "Use assign method\n";
    desrination1.assign(source.begin(), source.end());

    cout << "Use copy algorithm\n";
    copy(source.begin(), source.end(), desrination2.begin());
    desrination2.resize(2);

    cout << "The End" << endl;
    return 0;
}

3
我在自己的代码中几乎从不复制容器(这似乎是一件不太可能需要做的事情),因此我从未真正考虑过这个问题。 - anon
@onof 你如何复制STL容器? - user401947
我在 copy(source.begin(), source.end(), destination.size()); 处停止阅读。假设使用的是 std::copy()(我永远不明白为什么输入这五个字符会有多难),第三个参数是错误的。 - sbi
2个回答

6
目标的所有元素都会被销毁,然后调用目标大小次数的复制构造函数。为什么?不确定你在说什么。通常情况下,assign的实现类似于:
 template<class Iterator>
 void assign(Iterator first, Iterator last)
 {
      erase(begin(), end()); // Calls the destructor for each item
      insert(begin(), first, last); // Will not call destructor since it should use placemenet new
 }

使用剪贴板时,您可以像这样操作:

assert(source.size() <= destination.size());
destination.erase(copy(source.begin(), source.end(), destination.begin()), destination.end());

这应该是相同的内容。如果我确定源适合目标(稍微快一点,因为赋值/插入需要检查容器的容量),我会使用copy,否则我会使用assign,因为它最简单。另外,如果你使用copy并且目标太小,调用resize()是低效的,因为resize()将构造所有将被覆盖的元素。

GCC 4.5做得更好。通过赋值复制了源的所有元素,然后销毁了目标的额外元素。在两个平台上都可以使用第3种情况,然后调整大小(除了resize需要的一个默认构造函数)。这是一个玩具程序,展示了我的意思。

这是相同的东西。赋值是以复制构造实现的。

class A
{
     A& operator=(A other)
     {
         std::swap(*this, other);
         return *this;
     }

     // Same thing but a bit more clear
     A& operator=(const A& other)
     {
         A temp(other); // copy assignment
         std::swap(*this, temp);
         return *this;
     }
}

我应该看到什么?请更具体地指出您认为我发布的哪个部分是不正确的。 - ronag

2
如果你要复制整个容器,应该依赖于容器的复制构造函数或赋值运算符。但如果你只需要将容器内容从一个复制到另一个,最好使用std::copy
如果你使用的不止是POD对象,就无法为每个实例节省复制构造函数的调用。
你应该考虑使用共享/智能指针,在复制时只会增加它们的引用计数,并在修改实例时使用写时复制。

3
我不同意 std::copycontainer::assign赋值方面更好。使用 copy 必须手动设置容器的大小以适应源大小,否则需要使用 clear() 并使用插入迭代器。在第一种情况下,如果目标大于源,则需要创建默认初始化(如果可能的话--包含的元素不需要实现默认构造函数)以后进行重写。在第二种情况下,在清除容器后,可能需要多次分配内存才能重新增长。 - David Rodríguez - dribeas
1
assign的情况下,通常有更多的信息可用:实现知道容器类型、元素数量(如果迭代器是随机迭代器)、是否需要增长以及容器的初始状态。这些信息在std::copy中不可用,或者不能很好地利用源的大小。 - David Rodríguez - dribeas

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