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

123

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

bsruth
источник

Ответы:

117

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

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

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

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

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

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

Дрю Холл
источник
4
Причина, по которой цикл и вызов push_back плохи, заключается в том, что вы можете заставить вектор изменять размер несколько раз, если массив достаточно длинный.
bradtgmurray
@bradtgmurray: Я думаю, что любая разумная реализация конструктора вектора "два итератора", который я предложил выше, сначала вызывала бы std :: distance () на двух итераторах, чтобы получить необходимое количество элементов, а затем выделяла бы только один раз.
Дрю Холл
4
@bradtgmurray: Даже push_back () было бы неплохо из-за экспоненциального роста векторов (также известного как «амортизированное постоянное время»). Я думаю, что в худшем случае время выполнения будет примерно в 2 раза хуже.
Дрю Холл
2
И если вектор уже существует, vec.clear (); vec.insert (vec.begin (), a, a + n); тоже будет работать. Тогда вам даже не потребуется, чтобы a был указателем, а просто итератором, и назначение вектора было бы общим отказом (и способом C ++ / STL).
MP24,
6
Другой альтернативой, когда невозможно построить, будет assign : vec.assign(a, a+n), что будет более компактным, чем копирование и изменение размера.
mMontu
210

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

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

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

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. В этом случае необходимо вызвать изменение размера, поскольку memcpy работает за пределами вектора, и нет способа сообщить вектору, что его размер изменился. Помимо того, что это уродливое решение (байтовое копирование!), Помните, что это можно использовать только для типов POD . Я бы никогда не использовал это решение.

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

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

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

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

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

MattyT
источник
6
Вы не можете безопасно и переносимо ссылаться на "& dataArray [dataArraySize]" - это разыменование указателя / итератора за пределами конца. Вместо этого вы можете сказать dataArray + dataArraySize, чтобы получить указатель без предварительного разыменования.
Дрю Холл,
2
@Drew: да, можно, по крайней мере, в C. Он определен, что &exprне оценивает expr, а только вычисляет его адрес. И указатель один за последний элемент вполне допустимо, тоже.
Роланд Иллиг
2
Вы пробовали использовать метод 4 с 2? т.е. резервирование места перед вставкой. Похоже, что если размер данных большой, для нескольких вставок потребуется несколько перераспределений. Поскольку мы знаем размер априори, мы можем выполнить перераспределение перед вставкой.
Хорхе Лейтао
2
@MattyT в чем смысл метода 5? Зачем делать промежуточную копию данных?
Руслан
2
Лично я предпочел бы получить выгоду от автоматического распада массивов на указатели: dataVec.insert(dataVec.end(), dataArray, dataArray + dataArraySize);- мне кажется намного понятнее. Ничего не может получить от метода 5, только выглядит довольно неэффективным - если компилятор не сможет снова оптимизировать вектор.
Аконкагуа,
37

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

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

void CopyData(int *newData, size_t count)
{
   data.assign(newData, newData + count);
}
Torlack
источник
1
Простое для понимания и определенно самое быстрое решение (это просто скрытая память).
Дон Скотт
Deta.assign быстрее, чем data.insert?
Джим
10

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

Использование 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));
bsruth
источник
Я собирался рекомендовать этот подход.
mmocny 03
Скорее всего, будет более эффективно изменить размер вашего вектора заранее, если вы заранее знаете размер, а не использовать back_inserter.
люк
вы можете добавить my_data.reserve (size)
Дэвид Нехме
Обратите внимание, что внутренне это именно то, чего вы, кажется, хотите избежать. Это не копирование битов, это просто цикл и вызов push_back (). Я полагаю, вы просто хотели избежать ввода кода?
mmocny
1
Как не использовать конструктор векторов для копирования данных?
Мартин Йорк
3

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

Ассаф Лави
источник
8
Возможно, это должен быть комментарий к одному из других ответов, поскольку вы на самом деле не предлагаете решения.
finnw 01
3
int dataArray[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//source

unsigned dataArraySize = sizeof(dataArray) / sizeof(int);

std::vector<int> myvector (dataArraySize );//target

std::copy ( myints, myints+dataArraySize , myvector.begin() );

//myvector now has 1,2,3,...10 :-)
Антонио Рамаско
источник
2
Хотя этот фрагмент кода приветствуется и может оказать некоторую помощь, его можно было бы значительно улучшить, если бы он включал объяснение того, как и почему это решает проблему. Помните, что вы отвечаете на вопрос для будущих читателей, а не только для человека, который задает его сейчас! Пожалуйста , измените свой ответ , чтобы добавить объяснение, и дать указание о том , что применять ограничения и допущения.
Тоби Спейт
4
Подожди, а что myints?
mavavilj 02
2

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

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));
}
Шейн Пауэлл
источник
1

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

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

Томас Джонс-Лоу
источник
Если вы используете back_inserter, вам не нужно предварительно резервировать размер вектора, в который вы копируете. back_inserter выполняет push_back ().
Джон Диблинг,
0

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

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

Томас Джонс-Лоу
источник
Разве это не зависит от реализации std :: vector?
ReaperUnreal
Это ужасно! Вы заполняете массив дважды: сначала нулями, а затем правильными значениями. Просто выполните: std :: vector <int> myArray (source, source + item_count); и доверьте своему компилятору создание memcpy!
Крис Джефферсон
Доверьте своему компилятору создание __memcpy_int_aligned; это должно быть еще быстрее
MSalters