Почему при добавлении одного вектора к другому перемещать элементы дешевле, чем копировать их?

Допустим, у меня есть два вектора, src и dst, и я хочу добавить src в конец dst.

Я заметил, что большинство ответов по этой задаче рекомендуют следующее:

     dst.insert(dst.end(),
                std::make_move_iterator(src.begin()),
                std::make_move_iterator(src.end()));

Через это:

    dst.insert(dst.end(), src.begin(), src.end());

Насколько мне известно, вставка (вставка) элементов в вектор требует выделения пространства для вставленных элементов в конце вектора в обоих случаях, чтобы обеспечить непрерывность памяти, и я предполагаю, что стоимость copy и move одинакова в Это дело.

Перемещение объектов сделает их мгновенно разрушаемыми, это единственное преимущество от этого, или есть что-то еще, что мне не хватает?

edit: Можете ли вы объяснить это в этих двух случаях:

  1. Векторы содержат простые данные, например int.
  2. Векторы содержат объекты класса.

person Baraa    schedule 28.11.2016    source источник
comment
Эти две строки не эквивалентны - в первом случае вы не сможете использовать элементы в src после вставки. В обоих случаях вы выделите память для вектора, но первая версия будет перемещать элементы, а вторая - копировать их, и перемещение дешевле, чем копирование - если ваши элементы большой (например, векторы), это будет иметь огромное значение.   -  person Holt    schedule 28.11.2016
comment
Перемещение в целом дешевле копирования. Почему это удивительно?   -  person n. 1.8e9-where's-my-share m.    schedule 28.11.2016
comment
Да, для добавления элементов всегда требуется больше места. Но объем памяти, занимаемый процессом, будет расти только для вектора, а не для каждого элемента, если он содержит указатель на какое-то очень большое состояние.   -  person StoryTeller - Unslander Monica    schedule 28.11.2016
comment
Пытаюсь понять, почему их перемещение более эффективно .. В обоих случаях снова выделяю память?   -  person Baraa    schedule 28.11.2016
comment
@Baraa Вы выделяете память для векторных ячеек, а не память, управляемую элементами. Если ваши векторы содержат string, вам нужно будет выделить n ячеек в обоих случаях, но если вы переместитесь, вы скопируете только char* и int (предположим базовую реализацию), тогда как при копировании вам нужно будет повторно выделить новый char массив и скопируйте содержимое для каждой из n строк.   -  person Holt    schedule 28.11.2016
comment
Редактировать действительный вопрос, чтобы исключить действительные ответы, которые вы уже получили, - это плохой крикет. А теперь ваш вопрос бессвязен, так как он содержит важные критерии, которые дополняют ответ в виде приложения.   -  person Yakk - Adam Nevraumont    schedule 28.11.2016
comment
@Yakk, откатил на более раннюю версию.   -  person Baraa    schedule 28.11.2016


Ответы (3)


Действительно, если копирование и перемещение элемента стоит одинаково (в вашем примере с элементами типа int), то разницы нет.

Перемещение имеет значение только для элементов, которые сами хранят свои данные в куче, т.е. используют выделенную память (например, если элементы std::string или std::vector<something>). В этом случае перемещение или копирование элементов имеет (потенциально огромную) разницу (при условии, что конструктор перемещения и operator=(value_type&&) правильно реализованы / включены), поскольку перемещение просто копирует указатель в выделенную память, в то время как копия является глубокой: она выделяет новая память и копирует все данные, включая рекурсивные глубокие копии, если применимо.

Что касается затрат, связанных с данными, хранящимися в std::vector, есть некоторые затраты, если добавленные элементы превышают емкость. В этом случае будет изменен размер всего вектора, включая перемещение всех его элементов. Причина этого в том, что std::vector по спецификации хранит все свои элементы в одном массиве. Если добавление контейнеров - частая операция в вашем коде, вы можете рассмотреть другие контейнеры, такие как std::list или std::deque.

person Walter    schedule 28.11.2016

Я предполагаю, что стоимость копирования и перемещения одинакова.

Вы ошибаетесь.

Это не имеет ничего общего ни с вектором, ни с вставкой. Это связано с относительной разницей в стоимости конструктора.

ClassT::ClassT(const ClassT& orig);

и

ClassT::ClassT(ClassT&& orig);

Вы можете всегда найти конструктор перемещения, который дешевле или равно конструктору копирования.

Конструктор называется «конструктором перемещения», когда он принимает ссылку на rvalue в качестве параметра. Перемещать что-либо не обязательно, классу не обязательно иметь ресурс, который нужно переместить, и «конструктор перемещения» может не иметь возможности перемещать ресурс, как в допустимом (но, возможно, неразумном) случае, когда параметром является Ссылка на const rvalue (const T &&).

ClassT может выполнять те же операции, что и, например, конструктор копирования. Или ClassT мог бы лучше построить ход. Это означает, что если вам не нужны исходные экземпляры объектов в src при вставке в dst, вам следует использовать операцию перемещения.

person UmNyobe    schedule 28.11.2016
comment
Я почти уверен в этом :), но почему в этом примере дешевле? - person Baraa; 28.11.2016
comment
@Baraa, чтобы ответить на этот вопрос, нам нужно знать value_type вектора. - person Rerito; 28.11.2016
comment
Предположим, что это целочисленный тип. - person Baraa; 28.11.2016
comment
В большинстве случаев вы можете реализовать конструкцию перемещения, которая дешевле, чем построение копии. предположительно неверно, но определенно слишком неспецифично. - person Walter; 28.11.2016
comment
@Walter Я пытаюсь найти тип, для которого он не подходит. Но позвольте мне поправить. - person UmNyobe; 28.11.2016
comment
перемещение не дешевле, чем копирование для любого типа, который не управляет выделенной памятью (прямо или косвенно через ее элементы). Таким образом, любые POD и встроенные типы. Кстати, я не был одним из тех, кто проголосовал против. - person Walter; 28.11.2016

У тебя уже есть ответ. Но я думаю, что короткая программа может это хорошо проиллюстрировать:

#include <iostream>
#include <utility>
#include <vector>
#include <iterator>

struct expansive {
    static unsigned instances;
    static unsigned copies;
    static unsigned assignments;
    expansive() { ++instances; }
    expansive(expansive const&) { ++copies; ++instances; }
    expansive& operator=(expansive const&) { ++assignments; return *this; }
};

unsigned expansive::instances = 0;
unsigned expansive::copies = 0;
unsigned expansive::assignments = 0;

struct handle {
    expansive *h;

    handle() : h(new expansive) { }
    ~handle() { delete h; }

    handle(handle const& other) : h(new expansive(*other.h)) { }
    handle(handle&& other) : h(other.h) { other.h = nullptr; }
    handle& operator=(handle const& other) { *h = *other.h; return *this; }
    handle& operator=(handle&& other)  { std::swap(h, other.h); return *this; }
};

int main() {

  {
      std::vector<handle> v1(10), v2(10);

      v1.insert(end(v1), begin(v2), end(v2));

      std::cout << "When copying there were "
                << expansive::instances   << " instances of the object with " 
                << expansive::copies      << " copies and "
                << expansive::assignments << " assignments made." << std::endl;

  }

    expansive::instances = expansive::copies = expansive::assignments = 0;

  {
    std::vector<handle> v1(10), v2(10);

      v1.insert(end(v1), std::make_move_iterator(begin(v2)),
                       std::make_move_iterator(end(v2)));


      std::cout << "When moving there were "
                << expansive::instances   << " instances of the object with " 
                << expansive::copies      << " copies and "
                << expansive::assignments << " assignments made.\n";
  }

    return 0;
}

expansive моделирует ресурс, копирование которого требует больших затрат (представьте, что открываются дескрипторы файлов, сетевые соединения и т. Д.). Это ресурс, которым управляет handle. Чтобы поддерживать правильность программ, handle должен по-прежнему выполнять дорогостоящее копирование, когда оно копируется.

Теперь, когда программа запущена, она выдает следующий результат:

При копировании было 30 экземпляров объекта, 10 копий и 0 присвоений. При перемещении было выполнено 20 экземпляров объекта с 0 копиями и 0 выполненными назначениями.

Что это означает? Это означает, что если мы просто хотим передать дескрипторы в другой контейнер, мы должны проделать действительно обширную работу по пути (линейно по количеству ресурсов, которые мы пытаемся передать). Здесь нам на помощь приходит семантика перемещения.
При использовании move_iterator реальный дорогостоящий ресурс передается напрямую, а не реплицируется излишне. Это может привести к значительному увеличению производительности.

person StoryTeller - Unslander Monica    schedule 30.11.2016
comment
хороший пример, во всяком случае я уже был на примере строки :) - person Baraa; 30.11.2016
comment
@Baraa, я тебя не виню :) Просто подумал, что это может помочь будущим читателям, если есть пример, где они могут легко отследить, что происходит - person StoryTeller - Unslander Monica; 30.11.2016