std :: vector :: resize () проти std :: vector :: reserve ()


80

Існує потік в розділі коментарів в цьому записі про використання std::vector::reserve()vs. std::vector::resize().

Ось оригінальний код:

void MyClass::my_method()
{
    my_member.reserve(n_dim);
    for(int k = 0 ; k < n_dim ; k++ )
         my_member[k] = k ;
}

Я вважаю, що, щоб писати елементи в vector, правильно робити те, щоб зателефонувати std::vector::resize(), а не std::vector::reserve().

Насправді, наступний тестовий код "аварійно завершує роботу" у збірках налагодження у VS2010 SP1:

#include <vector>

using namespace std;

int main()
{
    vector<int> v;
    v.reserve(10);
    v[5] = 2;

    return 0;
}

Я маю рацію, чи я помиляюся? І чи правильно VS2010 SP1, чи це неправильно?


11
Пояснення може бути таким простим, як "Я помилявся": D
Лучіан Григоре

7
Я позначив це як "занадто локалізоване", оскільки @LuchianGrigore рідко помиляється
за замовчуванням

1
@Default читати "рідко неправильно" як "швидко виправляти свої помилки" :)
Luchian Grigore

1
Код в оригінальній публікації був оновлений для правильного використання resize(), і сумніви були зняті. Модераторам: сміливо видаляйте це запитання, якщо воно "занадто локалізоване", або залиште його, якщо вважаєте, що воно може допомогти комусь іншому в майбутньому.
Mr.C64,

1
це питання насправді очищає мої сумніви, коли я переношу свій проект з vc6 на vs2013 .. дякую :))
pengMiao

Відповіді:


114

Існує два різних методи з причини:

std::vector::reserve виділить пам'ять, але не змінить розмір вашого вектора, який матиме логічний розмір таким же, як і раніше.

std::vector::resizeнасправді змінить розмір вашого вектора та заповнить будь-який простір об’єктами у стані за замовчуванням. Якщо вони є ints, вони всі будуть дорівнювати нулю.

Після резервування у вашому випадку вам знадобиться багато push_backs для запису в елемент 5. Якщо ви не хочете цього робити, то у вашому випадку вам слід використовувати розмір.

Одне про резерв: якщо ви потім додасте елементи з push_back, поки не досягнете зарезервованої ємності, будь-які існуючі посилання, ітератори або вказівники на дані у вашому векторі залишатимуться дійсними. Отже, якщо я зарезервую 1000 і мій розмір 5, то &vec[4]буде залишатися незмінним, поки вектор не має 1000 елементів. Після цього я можу зателефонувати, push_back()і це буде працювати, але збережений покажчик &vec[4]раніше може бути недійсним.


отже, для порожнього вектора, тобто vec, після резервного vec [1] закінчуватиметься помилкою сегмента.
hailinzeng,

2
vec [1] буде невизначеною поведінкою.
CashCow

Чи std::vector::reserveзапобіжить випадкове копіювання повного масиву на push_back?
Допис самостійно

Це лише для C ++ 11 або конкретної реалізації std? Схоже, код із резервом і доступ із [] працює нормально? godbolt.org/z/MhgFdZ
Steve_Corrin

1
@Steve_Corrin Невизначена поведінка не визначена. Здається, це працює. Це все ще недійсний код. Індексування елементів, які не існують у < size()контейнері, не дозволяється. Вони там не існують, за визначенням мови. Якщо ваш компілятор вирішить не запускати нукери та підстановки, просто натискає / заглядає оперативну пам'ять так, як ви хочете, це просто удача. Або невдача, я здогадуюсь; в ідеалі ми могли б упізнати всі недійсні речі, які робили б усі програмісти, але удачі коли-небудь дістатись!
underscore_d

25

Тут відповів Ян Худек : Вибір між vector :: resize () та vector :: reserve ()

Ці дві функції роблять дуже різні речі.

Метод resize () (і передача аргументу конструктору еквівалентно цьому) буде вставляти задану кількість елементів у вектор (він має необов’язковий другий аргумент, щоб вказати їх значення). Це вплине на розмір (), ітерація пройде по всіх цих елементах, push_back вставить після них, і ви зможете безпосередньо отримати до них доступ за допомогою оператора [].

Метод reserve () виділяє лише пам'ять, але залишає її неініціалізованою. Це впливає лише на ємність (), але розмір () не зміниться. Для об'єктів немає значення, оскільки до вектора нічого не додається. Якщо потім вставити елементи, ніякого перерозподілу не відбудеться, оскільки це було зроблено заздалегідь, але це єдиний ефект.

Тож це залежить від того, що ви хочете. Якщо вам потрібен масив із 1000 елементів за замовчуванням, використовуйте resize (). Якщо ви хочете масив, до якого ви очікуєте вставити 1000 елементів і хочете уникнути пари виділень, використовуйте reserve ().

EDIT: Коментар Blastfurnace змусив мене ще раз прочитати питання і зрозуміти, що у вашому випадку правильна відповідь не попередньо розподіляється вручну. Просто продовжуйте вставляти елементи в кінці, як вам потрібно. Вектор автоматично перерозподілить за необхідності і зробить це ефективніше, ніж зазначено вручну. Єдиний випадок, коли резерв () має сенс, це коли ви маєте досить точну оцінку загального розміру, який вам буде легко доступний заздалегідь.

EDIT2: Редагування запитання про оголошення: якщо у вас є початкова оцінка, тоді резервуйте () цю оцінку, і якщо вона виявиться недостатньою, просто дозвольте вектору зробити це.


12

Це залежить від того, що ви хочете зробити. reserveнічого НЕ додавати елементи до vector; він лише змінює capacity(), що гарантує, що додавання елементів не буде перерозподіляти (і, наприклад, анулювати ітератори). resizeдодає елементи негайно. Якщо ви хочете додати елементи пізніше ( insert(), push_back()), використовуйте reserve. Якщо ви хочете отримати доступ до елементів пізніше (за допомогою []або at()), використовуйте resize. Отже, ти MyClass::my_methodможеш бути:

void MyClass::my_method()
{
    my_member.clear();
    my_member.reserve( n_dim );
    for ( int k = 0; k < n_dim; ++ k ) {
        my_member.push_back( k );
    }
}

або

void MyClass::my_method()
{
    my_member.resize( n_dim );
    for ( int k = 0; k < n_dim; ++ k ) {
        my_member[k] = k;
    }
}

Який із них ви вибрали - це питання смаку, але код, який ви цитуєте, явно неправильний.


3

Напевно, слід обговорити питання, коли обидва методи викликаються із числом, яке МЕНШЕ, ніж поточний розмір вектора.

Дзвінок reserve() з номером, меншим за пропускну здатність, не вплинуть на розмір або пропускну здатність.

Дзвінок resize() з номером, меншим за поточний розмір, контейнер буде зменшено до цього розміру, ефективно знищуючи зайві елементи.

Підсумовуючи resize(), звільниться пам’ять, тоді як reserve()ні.


Змінення розміру ніколи не звільняє пам’ять. Коли розмір стає меншим, будуть викликані деструктори, але пам'ять зберігається (ємність не змінюється).
Джон Гордон,

2

Так, ви праві, Лучіан щойно зробив друкарську помилку і, мабуть, занадто позбавлений кави, щоб усвідомити свою помилку.


1

resize фактично змінює кількість елементів у векторі, нові елементи створюються за замовчуванням, якщо зміна розміру змушує вектор рости.

vector<int> v;
v.resize(10);
auto size = v.size();

в цьому випадку розмір - 10.

з іншого боку, резерв вимагає лише збільшення внутрішнього буфера до вказаного розміру, але не змінює "розмір" масиву, змінюється лише розмір його буфера.

vector<int> v;
v.reserve(10);
auto size = v.size();

в цьому випадку розмір все ще 0.

Отже, щоб відповісти на ваше запитання, ви маєте рацію, навіть якщо у вас є достатньо місця, ви все одно отримуєте доступ до неініціалізованої пам'яті за допомогою оператора індексу. За допомогою int це не так погано, але у випадку з вектором класів ви отримаєте доступ до об'єктів, які не були побудовані.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.