Як ініціалізувати std :: vector з масиву стилю C?


174

Який найдешевший спосіб ініціалізувати std::vectorмасив зі стилю С?

Приклад: У наступному класі у мене є 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
Суть проблеми полягає в тому, що немає можливості вектору дізнатися, чи використовувався той самий розподільник для створення вашого масиву у стилі С. Як такий вектор повинен виділяти пам'ять, використовуючи власний розподільник. В іншому випадку він може просто замінити базовий масив і замінити його на свій масив.
Недійсна

Відповіді:


233

Не забувайте, що ви можете ставитися до покажчиків як до ітераторів:

w_.assign(w, w + len);

3
Це питання якості впровадження. Оскільки у ітераторів є теги, які визначають їх категорії, реалізація assign, безумовно, вільна, щоб використовувати їх для оптимізації; принаймні у VC ++, це дійсно робить саме це.
Павло Мінаєв

38
Швидке рішення може бути std :: вектор <double> w_ (w, w + len);
варення

1
Це копіює елементи в новостворену сховище для 'w_'; 'w_.data' не вказуватиме на 'w'. Ви все ще повинні розмістити "w". Передачі права власності немає
Indy9000

1
Якщо це один елемент минулого кінця, це повинно бути добре (так само, як v.end()ітератор, що вказує один минулий кінець з вектором у подібному випадку). Якщо ви все-таки отримаєте твердження, то щось не в іншому місці.
Павло Мінаєв

1
Тільки швидко, чи буде це розподіляти пам'ять масиву, коли вектор виходить за межі?
Адріан Кох

41

Ви використовуєте слово ініціалізація, тому незрозуміло, якщо це одноразове призначення або може статися багаторазово.

Якщо вам просто потрібна одноразова ініціалізація, ви можете помістити її в конструктор і використовувати два ітераторних конструктора:

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

В іншому випадку використовуйте призначення, як було запропоновано раніше:

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, як зазначено вище. Він все ще приймає масив у стилі С як перший аргумент. Просто трапляється взяти це за посиланням.


Як це працює

[Оновлення: див. Тут для більш всебічної дискусії щодо вивчення розміру]

Ось більш загальне рішення:

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

Ну, Павло був поруч, але є навіть більш просте і елегантне рішення ініціалізувати послідовний контейнер з масиву стилів змінного струму.

У вашому випадку:

w_ (array, std::end(array))
  • масив отримає нам вказівник на початок масиву (не вловив його ім'я),
  • std :: end (масив) отримає нам ітератор до кінця масиву.

1
Що вимагає / версія C ++ для цього потрібна?
Влад

1
Це один із конструкторів std :: vector, щонайменше, від c ++ 98 і далі .... Це називається "конструктор діапазону". cplusplus.com/reference/vector/vector/vector Спробуйте.
Магурел

1
Більш незалежна версія: w_ (std :: begin (масив), std :: end (масив)); (Надалі ви можете змінити масив C для контейнера C ++).
Андрій Романов

13
Зауважте, це працює лише у тому випадку, якщо у вас є реальний array(що зазвичай означає, що ви копіюєте з глобального або локального (оголошеного в поточній функції) масиву). У випадку з ОП він отримує вказівник і довжину, і оскільки це не передбачено довжиною, вони не можуть змінитись на отримання вказівника на розмірний масив чи що-небудь, тому std::endне буде працювати.
ShadowRanger

2
vectorне перевантажується operator(), тому це не компілюється. std::endназиватися покажчиком також не є корисним (питання запитує призначити вектор указівника та окрему змінну довжини). Це покращило б вашу відповідь, щоб показати більше контексту про те, що ви намагаєтесь запропонувати
ММ

2

Швидка загальна відповідь:

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

або конкретного питання:

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

на підставі вище : Не забувайте, що ви можете ставитися до покажчиків як до ітераторів


0

std::vector<double>::assignце шлях, тому що це мало коду . Але як це працює насправді? Чи не змінити його розмір, а потім скопіювати? У MS впровадженні STL я використовую це робить саме так.

Боюся, немає швидшого способу реалізувати (повторно) ініціалізувати своє std::vector.


що робити, якщо дані мають бути спільними між вектором та масивом? Чи потрібно нам копіювати щось у цьому випадку?
Влад

це відповідь чи запитання? що це підводить до вже існуючих відповідей?
Жан-Франсуа Фабре

@ Жан-ФрансуаFabre і що приносить ваш коментар? ;) правда, це погана відповідь, дана століттями тому.
Януш Ленар
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.