2010-07-31 3 views
3

Как скопировать контейнеры STL?C++: эффективно копировать контейнеры

// 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, за которым следует , размер делает то же самое на обеих платформах (кроме одного конструктора по умолчанию, который имеет размер). Вот игрушечная программа, которая показывает, что я имею в виду.

#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; 
} 
+2

В чем вопрос? – onof

+3

Я почти никогда не копирую контейнеры в свой собственный код (кажется, маловероятно, что вам нужно это делать), поэтому я никогда не рассматривал эту проблему. – 2010-07-31 08:37:20

+0

@onof Как вы копируете свои контейнеры STL? – user401947

ответ

4

Все элементы пункта назначения - уничтожены. Затем конструктор копирования вызывается столько раз, сколько размер адресата . Зачем?

Не уверен, что вы говорите. назначьте обычно реализуется как-то:

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()); 

, которые должны быть в значительной степени то же самое. Я бы использовал копию, если бы я точно знал, что источник поместится в пункт назначения (немного быстрее, так как для назначения/вставки нужно проверить емкость контейнера), иначе я бы использовал назначение, поскольку он прост. Также, если вы используете копию и место назначения слишком мало, вызов resize() неэффективен, так как resize() будет создавать все элементы, которые будут перезаписаны в любом случае.

GCC 4.5 лучше.Все элементы источника копируются через , а затем удаляются дополнительные элементы места назначения. Использование case 3 с последующим изменением размера делает то же самое на обеих платформах (за исключением одного конструктора по умолчанию, который требует ). Вот игрушечная программа , которая показывает, что я имею в виду.

Это то же самое. Назначение выполняется с точки зрения построения копии.

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; 
    } 
} 
+0

запустите мой код на визуальной студии и убедитесь сами. – user401947

+0

Что я могу увидеть? Пожалуйста, будьте более конкретными в той части моего сообщения, которое, по вашему мнению, неверно. – ronag

0

Если вы копируете весь контейнер, вам следует полагаться на конструктор или экземпляр контейнера. Но если вы копируете только содержимое контейнера от одного к другому, лучше всего использовать std::copy. Вы не можете сохранить вызов конструктора копирования для каждого экземпляра, если используете более чем объект POD.

Вам следует подумать об использовании общих/интеллектуальных указателей, которые будут только увеличивать свои счетчики ссылок при копировании и использовать копирование при записи при изменении вашего экземпляра.

+3

Я не согласен с тем, что 'std :: copy' лучше, чем' constainer :: assign' для * assign *. С помощью 'copy' вы должны вручную установить размер контейнера, чтобы он соответствовал размеру источника или' clear() 'и использовать итератор вставки. В первом случае, если пункт назначения больше источника, вам придется создать инициализацию по умолчанию (если это возможно - элемент, содержащий элементы, который не должен реализовывать конструктор по умолчанию), который будет позже перезаписан. Во втором случае после очистки контейнера может потребоваться многократное выделение. –

+1

В случае «присваивания» обычно доступно больше информации: реализация знает тип контейнера, количество элементов - если итераторы являются случайными итераторами - если это необходимо для роста и начальное состояние контейнера. Эта информация недоступна - или не может быть использована для использования в случае размера источника - в 'std :: copy'. –

Смежные вопросы