#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
помощью .
Подводя итог, у меня есть следующие вопросы:
- Почему я не могу использовать
d_arr.begin()
в качествеOutputIt
instd::copy
, когда базовый вектор имеет достаточную емкость? 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()
ofd_arr
?2. 1.
reserve()
предназначен только для выделения, а не для добавления элементов. Вместо этого вы должны использоватьresize()
.3. @NathanOliver, В выходных данных, которые я опубликовал, вы можете видеть, что если я резервирую 3, размер равен 0, а емкость равна 3
4. @ajoseps Нет. При использовании
reserve
вы просто выделяете память, фактические объекты (элементы вектора) не создаются. Вы должны использовать api vector для их создания (напримерpush_back
, whichback_inserter
uses .)5. Нет, для прямого копирования из одного вектора в другой вы просто делаете
destination = source;
. Это выполнит одно выделение и скопирует все элементы. Если у вас нет вектора, а есть другой контейнер с начальными и конечными итераторами, вы можете создать копию, напримерvector_type destination{std::begin(source), std::end(source)};
, если вы хотите добавить элементы в существующий вектор, затем используйтеinsert
orappend
с диапазоном итераторов, который также будет выполнять только одно выделение, если это необходимо.
Ответ №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
это не увеличивается на areserve
, поэтому то, чтоend() = array size
используется в цикле for на основе диапазона, не увидит новую емкость. Я думал о расширении этого сresize
помощью функции (также показательной)3. @NathanOliver кстати, если у вас есть предложения по улучшению, пожалуйста, не стесняйтесь их изменять. Но я просто хочу, чтобы это было просто, для людей, изучающих основы.