Avatar ·

Как объединить два std::vector в C++? Правильные способы конкатенации

Всем привет! Я недавно начал изучать C++ и столкнулся с задачей, которая кажется простой, но я не могу найти элегантного решения. Мне нужно объединить два std::vector в один. Например, у меня есть: ```cpp std::vector vec1 = {1, 2, 3, 4, 5}; std::vector vec2 = {6, 7, 8, 9, 10}; ``` Я хочу получить третий вектор `result`, который будет содержать все элементы из `vec1`, а затем все из `vec2`. То есть: `{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}`. Проблема в том, что я перепробовал несколько подходов, но все они кажутся мне либо неправильными, либо слишком громоздкими. Например: 1. Я пытался использовать `vec1.insert(vec1.end(), vec2.begin(), vec2.end())`, но это изменяет исходный вектор `vec1`, а я хочу сохранить оба исходных вектора неизменными. 2. Я пробовал написать цикл `for (int num : vec2) { vec1.push_back(num); }`, но это тоже меняет `vec1`. 3. Пытался создать `result` и использовать `std::copy`, но запутался с итераторами. Подскажите, пожалуйста, каков наиболее правильный и производительный способ объединения двух векторов в C++? Мне важно, чтобы исходные векторы остались нетронутыми. Желательно решение с использованием современного C++ (C++11 и новее). Заранее спасибо! <<>>

Самый простой и правильный способ: insert + reserve

Лучший способ объединить два std::vector без изменения исходных — создать новый вектор и использовать метод insert с предварительным резервированием памяти. Вот стандартное решение:

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec1 = {1, 2, 3, 4, 5};
    std::vector<int> vec2 = {6, 7, 8, 9, 10};

    // Шаг 1: создаём результирующий вектор
    std::vector<int> result;

    // Шаг 2: резервируем память сразу для всех элементов (оптимизация)
    result.reserve(vec1.size() + vec2.size());

    // Шаг 3: вставляем элементы первого вектора
    result.insert(result.end(), vec1.begin(), vec1.end());

    // Шаг 4: вставляем элементы второго вектора
    result.insert(result.end(), vec2.begin(), vec2.end());

    // Вывод результата
    for (int x : result) {
        std::cout << x << " ";
    }
    // Выведет: 1 2 3 4 5 6 7 8 9 10
    
    return 0;
}

Почему это работает и почему это лучший вариант?

  • Не изменяет исходные векторы — мы работаем только с новым result.
  • Метод insert с итераторами — копирует диапазон элементов из одного контейнера в другой. Это стандартный идиоматичный способ в C++.
  • reserve — выделяет память заранее, чтобы избежать множественных переаллокаций при вставке. Без reserve вектор мог бы перераспределять память несколько раз, что снижает производительность.
  • Сложность — O(N+M) по времени, где N и M — размеры векторов. Это оптимально, так как нужно скопировать все элементы.

Альтернативы с использованием инициализации списком (C++11)

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

std::vector<int> result(vec1.begin(), vec1.end());
result.insert(result.end(), vec2.begin(), vec2.end());

Это эквивалентно первому варианту, но без явного reserve (хотя многие компиляторы оптимизируют это). Для максимальной производительности всё же добавляйте reserve.

Использование std::merge (для отсортированных векторов)

Если ваши векторы уже отсортированы, std::merge — идеальный выбор:

#include <algorithm>

std::vector<int> result;
result.reserve(vec1.size() + vec2.size());
std::merge(vec1.begin(), vec1.end(), 
           vec2.begin(), vec2.end(), 
           std::back_inserter(result));
// Результат: {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} (только если исходные отсортированы!)

Что не стоит делать

  • Не используйте std::copy с std::back_inserter без reserve — это работает, но может быть медленнее из-за переаллокаций.
  • Избегайте циклов с push_back — это менее читаемо и не использует преимущества итераторов.

В современном C++ (C++17 и C++20) этот подход остаётся стандартом. Для пользовательских типов убедитесь, что у них есть корректный конструктор копирования.

<<>>

Использование std::move для эффективного перемещения (C++11)

Если исходные векторы больше не нужны, и вы хотите избежать копирования элементов (особенно для больших объектов), используйте семантику перемещения:

#include <vector>
#include <utility> // для std::move

int main() {
    std::vector<std::string> vec1 = {"apple", "banana"};
    std::vector<std::string> vec2 = {"cherry", "date"};

    std::vector<std::string> result;
    result.reserve(vec1.size() + vec2.size());

    // Перемещаем элементы из vec1 (vec1 становится пустым)
    result.insert(result.end(), 
                  std::make_move_iterator(vec1.begin()), 
                  std::make_move_iterator(vec1.end()));

    // Перемещаем элементы из vec2 (vec2 становится пустым)
    result.insert(result.end(), 
                  std::make_move_iterator(vec2.begin()), 
                  std::make_move_iterator(vec2.end()));

    // vec1 и vec2 теперь в "valid but unspecified" состоянии
    return 0;
}

Важно: После перемещения исходные векторы остаются в корректном, но неопределённом состоянии (обычно пустые). Используйте этот метод только если вам больше не нужны исходные данные.

Дополнительный совет: concatenation с помощью std::vector::swap

Если вы хотите добавить элементы из одного вектора в конец другого (изменяя один из них), можно использовать swap с временным вектором:

std::vector<int> vec1 = {1, 2, 3};
std::vector<int> vec2 = {4, 5, 6};

// Добавляем vec2 в конец vec1
vec1.insert(vec1.end(), vec2.begin(), vec2.end());
// vec1 = {1, 2, 3, 4, 5, 6}, vec2 не изменился

Этот подход изменяет один из исходных векторов, но не требует создания третьего вектора, что экономит память.