В чем преимущество перемещения диапазона элементов в векторе по сравнению с копированием?

#c #c 11 #iterator #move #stdvector

#c #c 11 #итератор #переместить #stdvector

Вопрос:

Рассмотрим следующий фрагмент (видел что-то аналогичное этому в большом коде моделирования)

 std::vector<int> v1{1,2,3,4,5,6,7};
std::vector<int> v2;

std::move(v1.begin()   2, v1.end(), back_inserter(v2));
  

Здесь я перемещаю диапазон элементов из v1 в v2 , но есть ли какое-либо особое преимущество в этом по сравнению с копированием? На самом деле я не вижу, в чем было бы преимущество move здесь, поскольку оно работает с диапазоном int s. На самом деле, я не верю, что происходит какое-либо перемещение, поскольку мы имеем дело с типами POD.

Если бы мы вместо этого хотели перенести все v1 в v2 , то мы могли бы сделать:

 v2 = std::move(v1);
  

Приведение здесь позволило бы v2 теперь владеть указателем на непрерывный диапазон памяти, ранее принадлежавший v1 , таким образом избегая копирования.

Но в первом перемещении диапазона элементов я не вижу пользы.

Комментарии:

1. Подумайте о векторе векторов

2. @Mat Верно, в этом случае я вижу выгоду. Я просто не вижу пользы в приведенном выше случае. Код, который я видел, действительно был вектором целых чисел, перемещение которого применялось к непрерывному диапазону элементов в векторе.

3. Как вы правильно заметили, семантика перемещения для модулей совпадает с семантикой копирования. Для типа, который фактически более эффективно реализует конструктор перемещения и / или назначение перемещения, вы бы заметили разницу?

4. Иногда вам нужно копировать, иногда вам нужно перемещать. Для этого конкретного варианта использования ваш вызов std::move (в первом примере) семантически ничем не отличается от std::copy последующего v1.erase(...) , но зачем это делать, когда одного вызова std::move было бы достаточно?

Ответ №1:

Здесь я перемещаю диапазон элементов из v1 в v2 , но есть ли какое-либо особое преимущество в этом по сравнению с копированием?

Нет. Здесь все произошло просто из-за изменения диапазона, потому что ваше использование std::move для примитивных типов просто справляется. Поэтому он делает то же самое, что и простой, если бы у вас было:

 std::vector<int> v2{v1.begin()   2, v1.end()};
  

Следовательно, вы правы в выводах. Однако это называется фундаментальные типы / примитивные типы, а не модули.


Но в первом перемещении диапазона элементов я не вижу пользы.

Рассмотрим случай std::vector</*expensive copy type*/> , в котором имеет смысл перемещать базовые элементы диапазона, когда это возможно.

Например, рассмотрим std::vector<std::string> случай

 std::vector<std::string> v1{ "1","2","3","4","5","6","7" };
std::vector<std::string> v2;
// reserve memory for unwanted reallocations
v2.reserve(std::distance(v1.begin()   2, v1.end()));

// moves the string element in the range
std::move(v1.begin()   2, v1.end(), back_inserter(v2));

// v1 now: 1 2
// v2 now: 3 4 5 6 7 
  

(Смотрите демонстрацию здесь)


В качестве дополнительного примечания, вместо std::move диапазона в отдельной строке, для итераторов можно также использовать std::make_move_iterator , для построения диапазона перемещения при объявлении (если сделать ошибку).

 #include <iterator> // std::make_move_iterator

std::vector<std::string> v1{ "1","2","3","4","5","6","7" };
std::vector<std::string> v2{ std::make_move_iterator(v1.begin()   2),
   std::make_move_iterator(v1.end()) };