Как инициализировать std :: vector из массива в стиле C?

174

Какой самый дешевый способ инициализации std::vectorиз массива в стиле C?

Пример: в следующем классе у меня есть vector, но из-за внешних ограничений данные будут переданы в виде массива в стиле C:

class Foo {
  std::vector<double> w_;
public:
  void set_data(double* w, int len){
   // how to cheaply initialize the std::vector?
}

Очевидно, я могу позвонить, w_.resize()а затем перебрать элементы или позвонить std::copy(). Есть ли лучшие методы?

Фрэнк
источник
11
Суть проблемы в том, что вектор не может узнать, использовался ли тот же распределитель для создания массива в стиле C. Как таковой вектор должен выделять память, используя свой собственный распределитель. В противном случае он может просто заменить базовый массив и заменить его вашим массивом.
Пустота

Ответы:

233

Не забывайте, что вы можете рассматривать указатели как итераторы:

w_.assign(w, w + len);
Павел Минаев
источник
3
Это вопрос качества реализации. Поскольку итераторы имеют теги, которые определяют их категории, реализация, assignбезусловно, может использовать их для оптимизации; по крайней мере, в VC ++, это действительно так.
Павел Минаев
38
Быстрое решение может быть: std :: vector <double> w_ (w, w + len);
jamk
1
Это копирует элементы во вновь созданное хранилище для 'w_'; «w_.data» не будет указывать на «w». Вы все еще должны освободить "W". Там нет передачи собственности
Indy9000
1
Если это один элемент после конца, все должно быть в порядке (так же, как v.end()итератор, указывающий один конец за концом вектором в аналогичном случае). Если вы получите утверждение, то что-то не так в другом месте.
Павел Минаев
1
Просто быстро, это освободит память массива, когда вектор выйдет из области видимости?
Адриан Кох
41

Вы используете слово initialize, поэтому неясно, является ли это однократным назначением или может происходить несколько раз.

Если вам нужна только однократная инициализация, вы можете поместить ее в конструктор и использовать векторный конструктор двух итераторов:

Foo::Foo(double* w, int len) : w_(w, w + len) { }

В противном случае используйте команду assign, как предлагалось ранее:

void set_data(double* w, int len)
{
    w_.assign(w, w + len);
}
Марк Б
источник
1
В моем случае назначение будет происходить неоднократно.
Франк
12

Вы можете «узнать» размер массива автоматически:

template<typename T, size_t N>
void set_data(const T (&w)[N]){
    w_.assign(w, w+N);
}

Надеюсь, вы можете изменить интерфейс на set_data, как указано выше. Он по-прежнему принимает массив в стиле C в качестве первого аргумента. Это просто происходит, чтобы взять его по ссылке.


Как это устроено

[Обновление: см. Здесь для более всестороннего обсуждения изучения размера]

Вот более общее решение:

template<typename T, size_t N>
void copy_from_array(vector<T> &target_vector, const T (&source_array)[N]) {
    target_vector.assign(source_array, source_array+N);
}

Это работает, потому что массив передается как ссылка на массив. В C / C ++ вы не можете передать массив как функцию, вместо этого он распадется на указатель, и вы потеряете размер. Но в C ++ вы можете передать ссылку на массив.

Передача массива по ссылке требует, чтобы типы точно совпадали. Размер массива является частью его типа. Это означает, что мы можем использовать параметр шаблона N, чтобы узнать размер для нас.

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

template<typename T, size_t N>
vector<T> convert_array_to_vector(const T (&source_array)[N]) {
    return vector<T>(source_array, source_array+N);
}
Аарон МакДейд
источник
1
В последнем примере return { begin(source_array), end(source_array) };также возможно
ММ
12

Что ж, Павел был близок, но есть даже более простое и элегантное решение для инициализации последовательного контейнера из массива стиля ac.

В твоем случае:

w_ (array, std::end(array))
  • массив получит указатель на начало массива (не поймал его имя),
  • std :: end (array) доставит нам итератор до конца массива.
Мугурел
источник
1
Что включает / версия C ++ это требует?
Влад
1
Это один из конструкторов std :: vector как минимум c ++ 98 и далее .... Он называется «конструктор диапазона». cplusplus.com/reference/vector/vector/vector Попробуйте.
Мугурел
1
Более независимая версия: w_ (std :: begin (array), std :: end (array)); (В будущем вы можете изменить массив C для контейнера C ++).
Андрей Романов
13
Имейте в виду, это работает, только если у вас есть реальный array(который обычно означает, что вы копируете из глобального или локального (объявленного в текущей функции) массива). В случае с OP он получает указатель и длину, и, поскольку длина не зависит от длины, они не могут перейти к получению указателя на размерный массив или что-либо еще, поэтому std::endне будут работать.
ShadowRanger
2
vectorне перегружается operator(), так что это не скомпилируется. std::endвызов по указателю также бесполезен (вопрос состоит в том, чтобы назначить вектор из указателя и отдельной переменной длины). Это улучшило бы ваш ответ, чтобы показать больше контекста о том, что вы пытаетесь предложить
MM
2

Быстрый общий ответ:

std::vector<double> vec(carray,carray+carray_size); 

или конкретный вопрос:

std::vector<double> w_(w,w+len); 

на основании выше : не забывайте, что вы можете рассматривать указатели как итераторы

eusoubrasileiro
источник
0

std::vector<double>::assignэто путь, потому что это маленький код . Но как это работает на самом деле? Разве это не изменить размер, а затем скопировать? В MS реализации STL я использую это именно так.

Боюсь, что нет более быстрого способа реализовать (пере) инициализацию вашей std::vector.

Януш Ленар
источник
Что делать, если данные должны быть разделены между вектором и массивом? Нужно ли что-нибудь копировать в этом случае?
Влад
это ответ или вопрос? что это дает уже существующим ответам?
Жан-Франсуа Фабр
@ Жан-Франсуа Фабр, а что принесет твой комментарий? ;) правда, это плохой ответ, даваемый давным-давно.
Януш Ленар