Почему std::copy() требует, чтобы std::back_inserter вставлялся в вектор с достаточной емкостью?

#c #c 11 #vector

#c #c 11 #вектор

Вопрос:

ССЫЛОЧНЫЙ КОД:

 #include <vector>
#include <algorithm>
#include <string>
#include <iostream>

void print(std::string label, std::vector<int> amp; arr) {
  std::cout << label << ":" << " size: " << arr.size() << " cap: " << arr.capacity() << " [ ";
  for (auto elem : arr) {
    std::cout << elem << " ";
  }
  std::cout << " ] " << std::endl;
}

void reserve_dest_use_begin() {
  std::vector<int> s_arr = {0, 1, 2, 3, 4, 5}; 
  print("source", s_arr);

  std::vector<int> d_arr;
  d_arr.reserve(3);
  print("dest", d_arr);

  auto min_elems = std::min(s_arr.size(), d_arr.capacity());

  std::cout << "COPYING FIRST" << min_elems << "3 FROM SOURCE TO DEST" << std::endl;

  std::copy(s_arr.begin(), s_arr.begin()   min_elems, d_arr.begin());

  print("source", s_arr);
  print("dest", d_arr);
}

void reserve_dest_use_back_inserter() {
  std::vector<int> s_arr = {0, 1, 2, 3, 4, 5}; 
  print("source", s_arr);

  std::vector<int> d_arr;
  d_arr.reserve(3);
  print("dest", d_arr);

  auto min_elems = std::min(s_arr.size(), d_arr.capacity());

  std::cout << "COPYING FIRST" << min_elems << " ELEMENTS FROM SOURCE TO DEST" << std::endl;

  std::copy(s_arr.begin(), s_arr.begin()   min_elems, std::back_inserter(d_arr));

  print("source", s_arr);
  print("dest", d_arr);
}

int main() {
  std::cout << "RESERVE DEST ARR. USE BEGIN() TO COPY" << std::endl;
  reserve_dest_use_begin();
  std::cout << "RESERVE DEST ARR. USE BACK_INSERTER() TO COPY" << std::endl;
  reserve_dest_use_back_inserter();
  

ВЫВОД:

 RESERVE DEST ARR USE BEGIN() TO COPY
source: size: 6 cap: 6 [ 0 1 2 3 4 5  ] 
dest: size: 0 cap: 3 [  ] 
COPYING FIRST 3 ELEMENTS FROM SOURCE TO DEST
source: size: 6 cap: 6 [ 0 1 2 3 4 5  ] 
dest: size: 0 cap: 3 [  ] 
=============================================
RESERVE DEST ARR USE BACK_INSERTER() TO COPY
source: size: 6 cap: 6 [ 0 1 2 3 4 5  ] 
dest: size: 0 cap: 3 [  ] 
COPYING FIRST 3 ELEMENTS FROM SOURCE TO DEST
source: size: 6 cap: 6 [ 0 1 2 3 4 5  ] 
dest: size: 3 cap: 3 [ 0 1 2  ]
  

В обоих сценариях целевой массив имеет достаточную емкость. Документация из cppreference указывает:

 Copies the elements in the range, defined by [first, last), to another range beginning at d_first.
1) Copies all elements in the range [first, last) starting from first and proceeding to last - 1. The behavior is undefined if d_first is within the range [first, last). In this case, std::copy_backward may be used instead.
  

d_arr.begin() Указывает на диапазон, который находится за пределами исходного диапазона [first, last) , но в приведенном примере мне нужно использовать std::back_inserter() для копирования вместо простого предоставления d_arr.begin() , несмотря на то, что базовый вектор имеет достаточную емкость.

std::back_inserter() Оптимизирована ли операция для простого перемещения блока памяти или она возвращает каждый элемент? Примечание из cppreference указывает:

 In practice, implementations of std::copy avoid multiple assignments and use bulk copy functions such as std::memmove if the value type is TriviallyCopyable and the iterator types satisfy LegacyContiguousIterator.
  

Однако, std::back_inserter() я подозреваю, что он не оптимизируется с memmove помощью .

Подводя итог, у меня есть следующие вопросы:

  1. Почему я не могу использовать d_arr.begin() в качестве OutputIt in std::copy , когда базовый вектор имеет достаточную емкость?
  2. std::back_inserter() Оптимизировано ли использование для массового копирования диапазонов?

РЕДАКТИРОВАТЬ: я думаю, что я подошел к этому вопросу с неправильной точки зрения. В комментариях было разъяснено, что операция, которую я хочу выполнить insert() , а не copy() . Мой конкретный вариант использования заключался в том, что я повторно clear() d_arr копировал и копировал подвектор из s_arr into d_arr . Я пытаюсь избежать перераспределения моего d_arr . Однако, поскольку d_arr он очищен, и хотя он обладает достаточной емкостью, он не имеет размера, что означает, что нет элементов для копирования. Вместо этого я на самом деле хочу вставить подвектор из s_arr into d_arr .

 d_arr.insert(d_arr.begin(), s_arr.begin(), s_arr.begin()   min_elems)
  

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

1. Позвольте мне спросить вас: Если вы это сделаете std::vector<int> d_arr; d_arr.reserve(3); , что такое size() of d_arr ?

2. 1. reserve() предназначен только для выделения, а не для добавления элементов. Вместо этого вы должны использовать resize() .

3. @NathanOliver, В выходных данных, которые я опубликовал, вы можете видеть, что если я резервирую 3, размер равен 0, а емкость равна 3

4. @ajoseps Нет. При использовании reserve вы просто выделяете память, фактические объекты (элементы вектора) не создаются. Вы должны использовать api vector для их создания (например push_back , which back_inserter uses .)

5. Нет, для прямого копирования из одного вектора в другой вы просто делаете destination = source; . Это выполнит одно выделение и скопирует все элементы. Если у вас нет вектора, а есть другой контейнер с начальными и конечными итераторами, вы можете создать копию, например vector_type destination{std::begin(source), std::end(source)}; , если вы хотите добавить элементы в существующий вектор, затем используйте insert or append с диапазоном итераторов, который также будет выполнять только одно выделение, если это необходимо.

Ответ №1:

Почему я не могу использовать d_arr.begin() в качестве вывода в std::copy, когда базовый вектор имеет достаточную емкость?

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

Оптимизирована ли операция std::back_inserter() только для memmove

Это может быть. Это может быть даже оптимизировано для чего-то более быстрого.

Оптимизировано ли использование std::back_inserter() для диапазонов массового копирования?

Учитывая достаточно умный оптимизатор, да.


Использование соответствующего конструктора vector было бы проще, чем std::copy . Как для читателя кода, так и для оптимизатора.

Ответ №2:

Может быть, это поможет, если я выражу простой вектор в коде.

 template <class T>
class Vector
{
private:
    unsigned int size = 0;
    unsigned int capacity = 10;
    T* array = new T[10];

public:
    void push_back(T constamp; item) {
        if (size >= capacity) {
            reserve(capacity * 2);
        }
        array[size] = item;
          size;
    }

    void reserve(unsigned int newCapacity) {
        if (size > capacity) {
            T* const temp = new T[newCapacity];
            std::copy(cbegin(), cend(), temp);
            delete [] std::exchange(array, temp);
            capacity = newCapacity;
        }
    }

    Tamp; operator[](unsigned int i) { return *array[i]; }

    // iterators
    T* begin() { return array; }
    T* end() { return array   size; }
    T const* cbegin() const { return array; }
    T const* cend() const { return array   size; }
};
  

Вы видите, что reserve это увеличивает выделенный размер для вектора, но не изменяет размер!

Когда вы используете цикл for на основе диапазона print , begin end используются итераторы and , которые внутренне используют размер векторов. Таким образом, будет доступен не весь внутренний массив.

И хотя запись в массив напрямую с использованием пользовательской конечной точки ( s_arr.begin() min_elems ), с достаточной емкостью для этого, возможно, не является «незаконной», это вообще не очень хорошая практика.

редактировать: что изменяет размер вектора resize , который внутренне выглядит примерно так

     void resize(unsigned int newSize, T constamp; val = T{}){
        if (newSize > capacity) {
            reserve(newSize);
        }
        if (newSize > size) {
            std::fill(begin() size, begin() newSize, val);
        }
        size = newSize;
    }
  

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

1. Мне это нравится, но ваш ответ неверен. Когда вы это делаете, new T[newCapacity] вы не только увеличиваете capcity, но и увеличиваете размер, поскольку теперь newCapacity в новом массиве есть элементы.

2. @NathanOliver это не фактическая реализация, это просто показательно. Я пытаюсь показать, что Vector::size это не увеличивается на a reserve , поэтому то, что end() = array size используется в цикле for на основе диапазона, не увидит новую емкость. Я думал о расширении этого с resize помощью функции (также показательной)

3. @NathanOliver кстати, если у вас есть предложения по улучшению, пожалуйста, не стесняйтесь их изменять. Но я просто хочу, чтобы это было просто, для людей, изучающих основы.