Подскажите, плз, как правильно добавить в конец vector'а сразу массив значений. Естественно, что можно добавлять поэлементно с помощью push_back, но сдаётся мне что есть более элегантный способ.
Re: добавить массив в std::vector
От:
Аноним
Дата:
14.06.06 08:16
Оценка:
Здравствуйте, aay, Вы писали:
aay>Подскажите, плз, как правильно добавить в конец vector'а сразу массив значений. Естественно, что можно добавлять поэлементно с помощью push_back, но сдаётся мне что есть более элегантный способ.
Здравствуйте, aay, Вы писали:
aay>Подскажите, плз, как правильно добавить в конец vector'а сразу массив значений. Естественно, что можно добавлять поэлементно с помощью push_back, но сдаётся мне что есть более элегантный способ.
std::vector<int> v;
v.push_back(1);
v.push_back(2);
int m[2] = {3, 4};
std::copy(m, m + 2, std::back_inserter(v));
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, "\n"));
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, aay, Вы писали:
aay>>Подскажите, плз, как правильно добавить в конец vector'а сразу массив значений. Естественно, что можно добавлять поэлементно с помощью push_back, но сдаётся мне что есть более элегантный способ.
А>#define DIM(arr) (sizeof(arr)/sizeof(arr[0]))
А>std::vector<int> vi; А>int ari[100]; А>// ... А>std::copy( &ari[0], &ari[ DIM(ari)], back_inserter( vi));
Здравствуйте, aay, Вы писали:
aay>ОК, большое спасибо. aay>А насколько этот вариант лучше/хуже цикла с push_back'ом?
back_inserter эстетичнее (он внутри использует что-то типа cont->push_back, можно самому глянуть, благо исходники stl открыты)
Здравствуйте, aay, Вы писали:
aay>ОК, большое спасибо. aay>А насколько этот вариант лучше/хуже цикла с push_back'ом?
back_inserter — это тот же самый push_back, только обёрнутый.
Поэтому лучше он только эстетически.
А вот практически — поэлементный push_back массива это зло.
Дело в том, что вектор резервирует под свои элементы место с некоторым запасом (v.capacity() >= v.size()), и когда этот запас исчерпывается — пересоздаёт внутренний массив бОльшего размера... выполняя копирование существующих элементов.
Обычно резервируется полутора-двукратный размер, т.е. v.reserve(v.size()*2), чтобы серия вставок не приводила к переразмещению каждый раз. И тем не менее, в среднем получается log(N) выделений памяти, N копирований; расход памяти — 3N единомоментно (старый массив размера N и новый размера 2N) и 2N постоянно.
Поэтому самый лучший способ — это
v.reserve(N);
v.insert(v.end(), arr, arr+N); // или серией push_back(), это уже не принципиально
> К>v.reserve(v.size() + N);
> К>v.insert(v.end(), arr, arr+N); // или серией push_back(), это уже не принципиально
> К>
push_back() медленнее, т.к.
1. При каждом push_back() происходит проверка, нужно ли увеличивать
внутрунний буфер, что немного замедляет работу
2. Компилятор может очень хорошо с'оптимизировать вызов insert (для
типов, которые допускают побайтное копирование), а push_back — наврядли
>> К>v.reserve(v.size() + N);
>> К>v.insert(v.end(), arr, arr+N); // или серией push_back(), это уже не принципиально
AD>push_back() медленнее, т.к. AD>1. При каждом push_back() происходит проверка, нужно ли увеличивать AD>внутрунний буфер, что немного замедляет работу AD>2. Компилятор может очень хорошо с'оптимизировать вызов insert (для AD>типов, которые допускают побайтное копирование), а push_back — наврядли
Поскольку template<class Iter> void insert(iterator where, Iter src_begin, Iter src_end) — шаблонный, то наивная реализация всё равно превратит его в серию push_back(), а умная — сама сделает однократное reserve.
Примерно так:
template<class Iter> void insert(iterator where, Iter src_begin, Iter src_end)
{
_insert_num(where-begin(), src_begin, src_end, iterator_traits<_Iter>::iterator_category());
}
// дефолтный (он же наивный) случайtemplate<class Iter> void _insert_num(size_type where, Iter src_begin, Iter src_end, input_iterator_tag)
{
for( ; src_begin != src_end; ++where, ++src_begin)
_insert_num_cnt(where, 1, src_begin);
}
// продвинутый случайtemplate<class Iter> void _insert_num(size_type where, Iter src_begin, Iter src_end, forward_iterator_tag)
{
size_type count = distance(src_begin,src_end);
_insert_num_cnt(where, count, src_begin);
}
// обратите внимание: переход от итератора к смещению (where) связан с тем, что итератор после вставки инвалидируется
// поэтому наивная реализация с её многократной вставкой была бы неработоспособнаtemplate<class Iter> void _insert_num_cnt(size_type where, size_type count, Iter src_begin)
{
// выполняется (если надо) переразмещение с переносом головной части + перенос хвостовой части
// затем copy-конструирование новых элементов на освободившемся месте
}
Этот код — по мотивам Dinkumware STL для VC2005.
Отсюда же можно сделать вывод: при работе с чистыми итераторами ввода (например, потоковыми) лучше не вставлять данные сразу в существующий вектор (особенно — если не в конец), а использовать промежуточный вектор.
// нужно:
v.insert(v.begin(), istream_iterator<int>(cin), istream_iterator<int>());
// делаем так:
vector<int> tmp;
tmp.insert(tmp.end(), istream_iterator<int>(cin), istream_iterator<int>()); // N копирований
v.insert(v.begin(), tmp); // сдвиг v.size() элементов и N копирований
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, aay, Вы писали:
aay>>ОК, большое спасибо. aay>>А насколько этот вариант лучше/хуже цикла с push_back'ом?
К>back_inserter — это тот же самый push_back, только обёрнутый. К>Поэтому лучше он только эстетически. К>А вот практически — поэлементный push_back массива это зло.
К>Дело в том, что вектор резервирует под свои элементы место с некоторым запасом (v.capacity() >= v.size()), и когда этот запас исчерпывается — пересоздаёт внутренний массив бОльшего размера... выполняя копирование существующих элементов. К>Обычно резервируется полутора-двукратный размер, т.е. v.reserve(v.size()*2), чтобы серия вставок не приводила к переразмещению каждый раз. И тем не менее, в среднем получается log(N) выделений памяти, N копирований; расход памяти — 3N единомоментно (старый массив размера N и новый размера 2N) и 2N постоянно.
К>Поэтому самый лучший способ — это К>
К>v.reserve(N);
К>v.insert(v.end(), arr, arr+N); // или серией push_back(), это уже не принципиально
К>
а resize() + memcpy() не быстрее получится?
не для всех случаев, конечно, подходит...
Здравствуйте, ArtDenis, Вы писали:
P>>а resize() + memcpy() не быстрее получится? P>>не для всех случаев, конечно, подходит...
AD>Точнее resize() + copy(). Это самый быстрый способ для данной задачи
Для типов с тривиальным конструктором — это то же самое, что и insert(end(), ...). А для типов с нетривиальным — делаем лишнее конструирование.
Здравствуйте, Кодт, Вы писали:
К>Для типов с тривиальным конструктором — это то же самое, что и insert(end(), ...).
Неа. Там нет сдвига элементов. Хотя там ничего не сдвигается (если вставлять в конец вектора), но формально код сдвига присутствует
К>А для типов с нетривиальным — делаем лишнее конструирование.
Да, правильно. Это я упустил из вида...
Здравствуйте, Кодт, Вы писали:
К>можно сделать вывод: при работе с чистыми итераторами ввода (например, потоковыми) лучше не вставлять данные сразу в существующий вектор (особенно — если не в конец), а использовать промежуточный вектор. К>
// нужно:
v.insert(v.begin(), istream_iterator<int>(cin), istream_iterator<int>());
// делаем так:
vector<int> tmp;
tmp.insert(tmp.end(), istream_iterator<int>(cin), istream_iterator<int>()); // N копирований
v.insert(v.begin(), tmp); // сдвиг v.size() элементов и N копирований
Что-то кажется мне, что при работе с istream_итераторами выжимать миллисекунды из вектора малоосмысленно. Вот если не в конец, то да. И вообще, std::vector MD, std::deque rulez (Sutter). Деки умеют куда эффективнее делать push_back, и умеют работать с другого конца.
Здравствуйте, aay, Вы писали:
aay>Подскажите, плз, как правильно добавить в конец vector'а сразу массив значений. Естественно, что можно добавлять поэлементно с помощью push_back, но сдаётся мне что есть более элегантный способ.
На самом деле есть книжка 'Effective STL' — там это все обсуждается достаточно хорошо.
Деталей не помню — но кажется смысл таков — для всякого рода вставок лучше использовать методы контейнера нежели алгоритмы и еще чего-то, поскольку методы контейнера знают специфику (непрерывность памяти или связный список) — поэтому могут оптимально делать.
А раз уж говорим про STL (а значит достаточно переносимый способ) — то всякие memcpy — это не то — лучше не пользовать.