Різниця вставки та push_back вектора C ++


79

Я хочу знати , що є різниця (s) між vector«s push_backі insertфункціями.

Чи існують структурні відмінності?

Чи є насправді велика різниця в продуктивності?


4
insertможе робити це де завгодно і включає інші речі, такі як підтримка діапазонів. push_backзручніше додавати в кінець.
Кріс,

Відповіді:


93

Найбільша різниця - це їх функціональність. push_backзавжди ставить новий елемент в кінці vectorі insertдозволяє вибрати позицію нового елемента. Це впливає на продуктивність. vectorелементи переміщуються в пам'ять лише тоді, коли необхідно збільшити її довжину, оскільки для цього виділено занадто мало пам'яті. З іншого боку, insertзмушує переміщати всі елементи після обраного положення нового елемента. Вам просто потрібно зробити для цього місце. Ось чому insertчасто може бути менш ефективним, ніж push_back.


9
З іншого боку, вставка в кінець повинна бути такою ж ефективною, як push_back.
Matthieu M.

18
Ну, майже настільки ж ефективний - код повинен визначити, що insertнасправді знаходиться в end(), отже, гілок буде більше, insertніж у push_back, якщо компілятор не зможе зрозуміти, що вони насправді не потрібні.
Якк - Адам Неврамон

5
@TomerW Люди вибирають C або C ++, щоб мати змогу вивчити кожен окремий цикл машини, щоб підвищити продуктивність, аж до інколи писати безпосередньо в ASM ... кожне "якщо" враховує, це не Java.
Cesar

1
@Cesar Це не те, чому ви вибираєте свій lang ... cpp може бути швидшим, ніж c, і навпаки ... пекло, в деяких випадках навіть Java може бути швидшим, ніж c, для своїх оптимізацій виконання.
Tomer W

@TomerW хо, то чому? просвіти мене, будь ласка.
Сезар,

32

Функції мають різне призначення. vector::insertдозволяє вставити об'єкт у визначеному положенні в vector, тоді як vector::push_backбуде просто наклеювати об'єкт на кінець. Дивіться наступний приклад:

using namespace std;
vector<int> v = {1, 3, 4};
v.insert(next(begin(v)), 2);
v.push_back(5);
// v now contains {1, 2, 3, 4, 5}

Ви можете використовувати insertдля виконання ту саму роботу, що і push_backз v.insert(v.end(), value).


9

Крім того, що push_back(x)робить те саме, що і insert(x, end())(можливо, з трохи кращою продуктивністю), є кілька важливих речей, які слід знати про ці функції:

  1. push_backіснує лише в BackInsertionSequenceконтейнерах - так, наприклад, він не існує в set. Це не могло, тому що push_back()дає вам змогу додавати його в кінці.
  2. Деякі контейнери також можуть задовольнити, FrontInsertionSequenceі вони мають push_front. Це задовольняє deque, але не vector.
  3. Це insert(x, ITERATOR)від InsertionSequence, що є загальним для setі vector. Таким чином ви можете використовувати будь-яку setабо vectorяк мішень для кількох вставок. Однак setмає додатково insert(x), що робить практично те саме (ця перша вставка в setозначає лише пришвидшення пошуку відповідного місця, починаючи з іншого ітератора - функція, яка не використовується в даному випадку).

Зверніть увагу на останній випадок, що якщо ви збираєтеся додавати елементи в цикл, то виконуйте container.push_back(x)і container.insert(x, container.end())будете робити те саме. Однак це не буде правдою, якщо ви отримаєте це container.end()спочатку, а потім використаєте його у цілому циклі.

Наприклад, ви можете ризикувати наступним кодом:

auto pe = v.end();
for (auto& s: a)
    v.insert(pe, v);

Це призведе до ефективного копіювання цілого aу vвектор у зворотному порядку , і лише якщо вам пощастить не перерозподілити вектор для розширення (ви можете запобігти цьому, зателефонувавши reserve()спочатку); якщо вам не так пощастило, ви отримаєте так зване UndefinedBehavior (tm). Теоретично це заборонено, оскільки ітератори вектора вважаються недійсними кожного разу, коли додається новий елемент.

Якщо ви робите це таким чином:

copy(a.begin(), a.end(), back_inserter(v);

він буде скопійований aв кінці vв оригіналі замовлення, і це не несе ризику анулювання ітератора.

[EDIT] Раніше я зробив, щоб цей код виглядав таким чином, і це було помилкою, оскільки inserterнасправді підтримує дійсність і просування ітератора:

copy(a.begin(), a.end(), inserter(v, v.end());

Отже, цей код також додасть усі елементи у початковому порядку без будь-якого ризику.


Ваші зауваження щодо того, що std :: inserter є "ризикованим" для векторних контейнерів, є неправильними. Насправді він призначений для усунення цих ризиків. Ніякого зворотного впорядкування, жодної невизначеної поведінки. Див. Напр. Fluentcpp.com/2017/10/06/stl-inserter-iterators-work щодо розподілу.
downhillFromHere

Ти маєш рацію. Я просто хотів зробити код коротким, замість того, щоб розширювати його до розширеного ручного циклу, тому це насправді не відповідає дійсності, оскільки inserterпідтримує вдосконалення та обґрунтованість ітератора. Мені доведеться відредагувати пост :)
Етуріс,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.