Как скопировать содержимое массива в std::vector на С++ без зацикливания?

У меня есть массив значений, который передается моей функции из другой части программы, которую мне нужно сохранить для последующей обработки. Поскольку я не знаю, сколько раз будет вызываться моя функция, прежде чем придет время обрабатывать данные, мне нужна динамическая структура хранения, поэтому я выбрал std::vector. Я не хочу делать стандартный цикл для push_back всех значений по отдельности, было бы неплохо, если бы я мог просто скопировать все это, используя что-то похожее на memcpy.


person bsruth    schedule 03.11.2008    source источник


Ответы (10)


Если вы можете построить вектор после того, как получили массив и размер массива, вы можете просто сказать:

std::vector<ValueType> vec(a, a + n);

... предполагая, что a - это ваш массив, а n - количество содержащихся в нем элементов. В противном случае std::copy() с resize() поможет.

Я бы держался подальше от memcpy(), если вы не можете быть уверены, что значения являются типами обычных данных (POD).

Кроме того, стоит отметить, что ни один из них на самом деле не избегает цикла for - это просто вопрос того, должны ли вы видеть его в своем коде или нет. Производительность O(n) во время выполнения неизбежна для копирования значений.

Наконец, обратите внимание, что массивы в стиле C являются вполне допустимыми контейнерами для большинства алгоритмов STL — необработанный указатель эквивалентен begin(), а (ptr + n) эквивалентен end().

person Drew Hall    schedule 03.11.2008
comment
Причина, по которой зацикливание и вызов push_back — это плохо, заключается в том, что вы можете принудительно изменить размер вектора несколько раз, если массив достаточно длинный. - person bradtgmurray; 03.11.2008
comment
@bradtgmurray: я думаю, что любая разумная реализация конструктора векторов с двумя итераторами, который я предложил выше, будет сначала вызывать std::distance() для двух итераторов, чтобы получить необходимое количество элементов, а затем выделять только один раз. - person Drew Hall; 04.11.2008
comment
@bradtgmurray: Даже push_back() не так уж плох из-за экспоненциального роста векторов (также известного как амортизированное постоянное время). Я думаю, что в худшем случае время выполнения будет примерно в 2 раза хуже. - person Drew Hall; 04.11.2008
comment
И если вектор уже есть, vec.clear(); vec.insert(vec.begin(), а, а + п); тоже будет работать. Тогда вам даже не потребуется, чтобы a был указателем, просто итератором, и назначение вектора было бы общим с ошибкой (и способом C++/STL). - person MP24; 07.11.2008
comment
Другой альтернативой, когда невозможно построить, будет assign: vec.assign(a, a+n), что будет более компактным, чем копирование и изменение размера. - person mMontu; 28.10.2013

Здесь было много ответов, и почти все они выполнят свою работу.

Однако есть несколько вводящих в заблуждение советов!

Вот варианты:

vector<int> dataVec;

int dataArray[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
unsigned dataArraySize = sizeof(dataArray) / sizeof(int);

// Method 1: Copy the array to the vector using back_inserter.
{
    copy(&dataArray[0], &dataArray[dataArraySize], back_inserter(dataVec));
}

// Method 2: Same as 1 but pre-extend the vector by the size of the array using reserve
{
    dataVec.reserve(dataVec.size() + dataArraySize);
    copy(&dataArray[0], &dataArray[dataArraySize], back_inserter(dataVec));
}

// Method 3: Memcpy
{
    dataVec.resize(dataVec.size() + dataArraySize);
    memcpy(&dataVec[dataVec.size() - dataArraySize], &dataArray[0], dataArraySize * sizeof(int));
}

// Method 4: vector::insert
{
    dataVec.insert(dataVec.end(), &dataArray[0], &dataArray[dataArraySize]);
}

// Method 5: vector + vector
{
    vector<int> dataVec2(&dataArray[0], &dataArray[dataArraySize]);
    dataVec.insert(dataVec.end(), dataVec2.begin(), dataVec2.end());
}

Короче говоря, метод 4 с использованием vector::insert лучше всего подходит для сценария bsruth.

Вот некоторые кровавые подробности:

Способ 1, вероятно, проще всего понять. Просто скопируйте каждый элемент из массива и поместите его в конец вектора. Увы, медленно. Поскольку существует цикл (подразумеваемый функцией копирования), каждый элемент должен обрабатываться индивидуально; никакие улучшения производительности не могут быть сделаны на основе того факта, что мы знаем, что массив и векторы являются смежными блоками.

Метод 2 – это рекомендуемое улучшение производительности по сравнению с методом 1; просто предварительно зарезервируйте размер массива перед его добавлением. Для больших массивов это может помочь. Однако лучший совет здесь никогда не использовать резерв, если профилирование не предполагает, что вы можете получить улучшение (или вам нужно убедиться, что ваши итераторы не будут признаны недействительными). Бьерн согласен. Между прочим, я обнаружил, что этот метод работает медленнее большую часть времени, хотя я изо всех сил пытаюсь всесторонне объяснить, почему он регулярно значительно медленнее, чем метод 1...

Метод 3 — это решение старой школы — добавьте к проблеме немного C! Работает отлично и быстро для типов POD. В этом случае требуется вызов resize, так как memcpy работает за пределами вектора, и нет способа сообщить вектору, что его размер изменился. Помимо того, что это уродливое решение (копирование байтов!), помните, что это можно использовать только для типов POD. Я бы никогда не использовал это решение.

Способ 4 — лучший способ. Смысл понятен, он (обычно) самый быстрый и работает для любых объектов. Нет никаких недостатков в использовании этого метода для этого приложения.

Метод 5 является усовершенствованием метода 4: скопируйте массив в вектор, а затем добавьте его. Хороший вариант - в целом быстро и понятно.

Наконец, вы знаете, что вместо массивов можно использовать векторы, верно? Даже когда функция ожидает массивы в стиле c, вы можете использовать векторы:

vector<char> v(50); // Ensure there's enough space
strcpy(&v[0], "prefer vectors to c arrays");

Надеюсь, это поможет кому-то там!

person MattyT    schedule 04.11.2008
comment
@ Method1, std::copy может использовать трейты для оптимизации копии (существуют реализации). В частности, копирование из вектора в вектор без использования back_inserters, скорее всего, будет быстрее, чем memcpy(). std::memcpy() должен работать с невыровненной памятью. std::copy‹int*›() — нет. - person MSalters; 04.11.2008
comment
Вы не можете безопасно и переносимо ссылаться на &dataArray[dataArraySize] - это разыменование указателя/итератора за концом. Вместо этого вы можете сказать dataArray + dataArraySize, чтобы получить указатель без предварительного разыменования. - person Drew Hall; 07.11.2008
comment
@Drew: да, вы можете, по крайней мере, в C. Определено, что &expr не оценивает expr, он только вычисляет его адрес. И указатель one за последним элементом тоже вполне корректен. - person Roland Illig; 27.05.2011
comment
Вы пытались сделать метод 4 с 2? то есть резервирование места перед вставкой. Кажется, что если размер данных большой, для нескольких вставок потребуется несколько перераспределений. Поскольку мы знаем размер априори, мы можем выполнить перераспределение перед вставкой. - person Jorge Leitao; 31.12.2013
comment
все эти предложения, кроме метода 5, имеют неопределенное поведение, если исходный массив пуст - person jyavenard; 11.05.2018
comment
@jyavenard ISO C++ forbids zero-size array ‘dataArray’ [-Wpedantic] - person Ruslan; 21.12.2019
comment
@MattyT, в чем смысл метода 5? Зачем делать промежуточную копию данных? - person Ruslan; 21.12.2019
comment
Лично я бы предпочел, чтобы массивы автоматически разлагались на указатели: dataVec.insert(dataVec.end(), dataArray, dataArray + dataArraySize); — мне кажется намного понятнее. Не может ничего получить и от метода 5, только выглядит довольно неэффективным — если только компилятор не сможет снова оптимизировать вектор. - person Aconcagua; 19.02.2020
comment
Для примитивных типов данных метод 4 по-прежнему менее эффективен, чем использование std::unique_ptr + memcpy, потому что он сводится к memmove, которому предшествует несколько ветвей if. - person Mikhail Vasilyev; 18.07.2021

Если все, что вы делаете, это заменяете существующие данные, то вы можете сделать это

std::vector<int> data; // evil global :)

void CopyData(int *newData, size_t count)
{
   data.assign(newData, newData + count);
}
person Torlack    schedule 03.11.2008
comment
Простое для понимания и, безусловно, самое быстрое решение (это просто memcpy за кулисами). - person Don Scott; 21.12.2014
comment
Является ли deta.assign быстрее, чем data.insert? - person Jim; 26.01.2018

std::copy — это то, что вам нужно.

person luke    schedule 03.11.2008

Поскольку я могу редактировать только свой собственный ответ, я собираюсь сделать составной ответ из других ответов на мой вопрос. Спасибо всем, кто ответил.

Используя std::copy, это все еще выполняется в фоновом режиме, но вы не не нужно вводить код.

int foo(int* data, int size)
{
   static std::vector<int> my_data; //normally a class variable
   std::copy(data, data + size, std::back_inserter(my_data));
   return 0;
}

Используя обычный memcpy. Это, вероятно, лучше всего использовать для базовых типов данных (например, int), но не для более сложных массивов структур или классов.

vector<int> x(size);
memcpy(&x[0], source, size*sizeof(int));
person bsruth    schedule 03.11.2008
comment
Я собирался рекомендовать этот подход. - person mmocny; 03.11.2008
comment
Скорее всего, более эффективно изменить размер вашего вектора заранее, если вы знаете размер заранее, а не использовать back_inserter. - person luke; 03.11.2008
comment
вы можете добавить my_data.reserve(size) - person David Nehme; 03.11.2008
comment
Обратите внимание, что внутри это делает именно то, чего вы, кажется, хотите избежать. Это не копирование битов, это просто зацикливание и вызов push_back(). Я думаю, вы просто хотели избежать ввода кода? - person mmocny; 03.11.2008
comment
Почему бы не использовать векторный конструктор для копирования данных? - person Martin York; 03.11.2008
comment
потому что это будет работать только для первой итерации. При добавлении дополнительных данных на последующих итерациях я не могу просто использовать конструктор. - person bsruth; 03.11.2008
comment
Это здорово (std::copy), потому что дает больше гибкости, особенно если вы не хотите копировать весь вектор и можете добавить другой вектор. - person Chef Pharaoh; 10.12.2019

избегайте memcpy, говорю я. Нет причин возиться с операциями с указателями, если вам это действительно не нужно. Кроме того, он будет работать только для типов POD (например, int), но не будет работать, если вы имеете дело с типами, требующими построения.

person Assaf Lavie    schedule 03.11.2008
comment
Возможно, это должен быть комментарий к одному из других ответов, поскольку на самом деле вы не предлагаете решение. - person finnw; 01.07.2013

Еще один ответ, поскольку человек сказал: «Я не знаю, сколько раз будет вызываться моя функция», вы можете использовать метод вставки вектора, например, чтобы добавить массивы значений в конец вектора:

vector<int> x;

void AddValues(int* values, size_t size)
{
   x.insert(x.end(), values, values+size);
}

Мне нравится этот способ, потому что реализация вектора должна оптимизироваться для наилучшего способа вставки значений на основе типа итератора и самого типа. Вы несколько отвечаете на реализацию stl.

Если вам нужно гарантировать максимальную скорость, и вы знаете, что ваш тип является типом POD, я бы рекомендовал метод изменения размера в ответе Томаса:

vector<int> x;

void AddValues(int* values, size_t size)
{
   size_t old_size(x.size());
   x.resize(old_size + size, 0);
   memcpy(&x[old_size], values, size * sizeof(int));
}
person Shane Powell    schedule 03.11.2008

В дополнение к методам, представленным выше, вам необходимо убедиться, что вы используете либо std::Vector.reserve(), std::Vector.resize(), либо создаете вектор по размеру, чтобы убедиться, что ваш вектор имеет достаточно элементов в это для хранения ваших данных. в противном случае вы испортите память. Это справедливо как для std::copy(), так и для memcpy().

Это причина использования vector.push_back(), вы не можете писать дальше конца вектора.

person Thomas Jones-Low    schedule 03.11.2008
comment
Если вы используете back_inserter, вам не нужно предварительно резервировать размер вектора, в который вы копируете. back_inserter выполняет функцию push_back(). - person John Dibling; 03.11.2008

Предполагая, что вы знаете, насколько велик элемент в векторе:

std::vector<int> myArray;
myArray.resize (item_count, 0);
memcpy (&myArray.front(), source, item_count * sizeof(int));

http://www.cppreference.com/wiki/stl/vector/start

person Thomas Jones-Low    schedule 03.11.2008
comment
Разве это не зависит от реализации std::vector? - person ReaperUnreal; 03.11.2008
comment
Это ужасно! Вы заполняете массив дважды, один с «0», затем с правильными значениями. Просто выполните: std::vector‹int›myArray(source, source + item_count); и доверьте компилятору создание memcpy! - person Chris Jefferson; 03.11.2008
comment
Доверьте своему компилятору создание __memcpy_int_aligned; это должно быть еще быстрее - person MSalters; 04.11.2008

person    schedule
comment
Хотя этот фрагмент кода приветствуется и может оказать некоторую помощь, он был бы значительно улучшен, если бы включал объяснение того, как и почему это решает проблему. Помните, что вы отвечаете на вопрос читателей в будущем, а не только того, кто задает сейчас! Пожалуйста, отредактируйте свой ответ, чтобы добавить объяснение и указать, какие ограничения и предположения применяются. - person Toby Speight; 01.03.2017
comment
Подождите, что такое myints? - person mavavilj; 02.08.2018