Vector addition
Programming
Duplicate content
Data structures
Coding tutorials

Appending a vector to a vector

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The most common way to append one vector to another in C++ is vec1.insert(vec1.end(), vec2.begin(), vec2.end()). This copies all elements from vec2 to the end of vec1 in a single operation. For move semantics, use std::make_move_iterator. For pre-allocation, call vec1.reserve() before the insert. This article covers every method available in modern C++ (C++11 through C++23), their performance characteristics, and the trade-offs between them.

Method 1: insert (The Standard Approach)

The insert member function is the most idiomatic way to append one vector to another.

cpp
1#include <vector>
2#include <iostream>
3
4int main() {
5    std::vector<int> vec1 = {1, 2, 3};
6    std::vector<int> vec2 = {4, 5, 6};
7
8    vec1.insert(vec1.end(), vec2.begin(), vec2.end());
9
10    for (int val : vec1) {
11        std::cout << val << " ";
12    }
13    // Output: 1 2 3 4 5 6
14}

The range-based insert handles memory allocation internally. If vec1 does not have enough capacity, it reallocates once and copies everything. This makes it both simple and efficient.

Method 2: insert with reserve (Optimal Performance)

If you know the combined size ahead of time, calling reserve before insert guarantees at most one reallocation.

cpp
1#include <vector>
2
3int main() {
4    std::vector<int> vec1 = {1, 2, 3};
5    std::vector<int> vec2 = {4, 5, 6};
6
7    vec1.reserve(vec1.size() + vec2.size());
8    vec1.insert(vec1.end(), vec2.begin(), vec2.end());
9    // vec1: {1, 2, 3, 4, 5, 6}
10}

In practice, most standard library implementations of insert already calculate the needed capacity when given random-access iterators. But calling reserve explicitly makes your intent clear and guarantees the behavior regardless of implementation.

Method 3: std::copy with back_inserter

The std::copy algorithm combined with std::back_inserter achieves the same result through a different mechanism.

cpp
1#include <vector>
2#include <algorithm>
3#include <iterator>
4
5int main() {
6    std::vector<int> vec1 = {1, 2, 3};
7    std::vector<int> vec2 = {4, 5, 6};
8
9    vec1.reserve(vec1.size() + vec2.size());
10    std::copy(vec2.begin(), vec2.end(), std::back_inserter(vec1));
11    // vec1: {1, 2, 3, 4, 5, 6}
12}

The reserve call is more important here than with insert. Without it, back_inserter calls push_back for each element individually, which can trigger multiple reallocations. With reserve, it is a single allocation followed by N copy operations.

Method 4: Move semantics (C++11)

When you no longer need the source vector's elements, moving avoids expensive copies. This matters for vectors of objects like std::string or large structs.

cpp
1#include <vector>
2#include <string>
3#include <iterator>
4
5int main() {
6    std::vector<std::string> vec1 = {"hello", "world"};
7    std::vector<std::string> vec2 = {"foo", "bar", "baz"};
8
9    vec1.insert(vec1.end(),
10                std::make_move_iterator(vec2.begin()),
11                std::make_move_iterator(vec2.end()));
12
13    // vec1: {"hello", "world", "foo", "bar", "baz"}
14    // vec2 elements are in a valid but unspecified state
15}

After the move, vec2 still has the same number of elements, but each element is in a moved-from state. For std::string, that typically means each element is now an empty string.

Method 5: append_range (C++23)

C++23 introduces append_range, which provides the cleanest syntax.

cpp
1#include <vector>
2
3int main() {
4    std::vector<int> vec1 = {1, 2, 3};
5    std::vector<int> vec2 = {4, 5, 6};
6
7    vec1.append_range(vec2);
8    // vec1: {1, 2, 3, 4, 5, 6}
9}

This is equivalent to the range-based insert but reads more naturally. It works with any range, not just vectors.

Method 6: Concatenation into a new vector

Sometimes you want to leave both source vectors unchanged and produce a new combined vector.

cpp
1#include <vector>
2
3int main() {
4    std::vector<int> vec1 = {1, 2, 3};
5    std::vector<int> vec2 = {4, 5, 6};
6
7    std::vector<int> combined;
8    combined.reserve(vec1.size() + vec2.size());
9    combined.insert(combined.end(), vec1.begin(), vec1.end());
10    combined.insert(combined.end(), vec2.begin(), vec2.end());
11    // combined: {1, 2, 3, 4, 5, 6}
12    // vec1 and vec2 are unchanged
13}

The reserve call ensures exactly one allocation for the combined vector.

Comparison of Methods

MethodCopies/MovesRequires ReserveC++ StandardBest For
insert(end, begin, end)CopiesOptional (often optimized)C++98General use
insert + reserveCopiesYes (explicit)C++98Performance-critical code
std::copy + back_inserterCopiesRecommendedC++98Algorithm-based pipelines
insert + make_move_iteratorMovesOptionalC++11Expensive-to-copy elements
append_rangeCopiesOptionalC++23Modern codebases
Two inserts into new vectorCopiesRecommendedC++98Preserving both source vectors

Performance Considerations

Reallocation Cost

Vector reallocation involves allocating new memory, copying (or moving) all existing elements, and freeing the old memory. For a vector of N elements, each reallocation is O(N). Without reserve, appending M elements could trigger O(log M) reallocations because vectors typically grow by a factor of 1.5x or 2x.

Benchmark: insert vs push_back Loop

Appending 1 million integers using different methods:

cpp
1#include <vector>
2#include <chrono>
3#include <iostream>
4
5int main() {
6    const int N = 1'000'000;
7    std::vector<int> source(N, 42);
8
9    // Method 1: insert
10    {
11        std::vector<int> dest;
12        auto start = std::chrono::high_resolution_clock::now();
13        dest.insert(dest.end(), source.begin(), source.end());
14        auto end = std::chrono::high_resolution_clock::now();
15        auto ms = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
16        std::cout << "insert: " << ms.count() << " us\n";
17    }
18
19    // Method 2: push_back loop
20    {
21        std::vector<int> dest;
22        dest.reserve(N);
23        auto start = std::chrono::high_resolution_clock::now();
24        for (int val : source) {
25            dest.push_back(val);
26        }
27        auto end = std::chrono::high_resolution_clock::now();
28        auto ms = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
29        std::cout << "push_back loop: " << ms.count() << " us\n";
30    }
31}

The insert approach is generally faster because it can use memcpy or memmove for trivially copyable types, while push_back operates element by element.

Exception Safety

The range-based insert provides the strong exception guarantee for trivially copyable types and the basic exception guarantee otherwise. If an exception is thrown during copying, the vector is left in a valid but potentially partially-extended state. Using reserve beforehand ensures no reallocation happens during the insert, which simplifies the exception behavior.

Appending Vectors of Different Types

If the source and destination vectors have different but convertible element types, use insert with implicit conversion:

cpp
1#include <vector>
2
3int main() {
4    std::vector<double> doubles = {1.5, 2.5, 3.5};
5    std::vector<int> ints = {4, 5, 6};
6
7    // Convert ints to doubles during insertion
8    doubles.insert(doubles.end(), ints.begin(), ints.end());
9    // doubles: {1.5, 2.5, 3.5, 4.0, 5.0, 6.0}
10}

This works because int is implicitly convertible to double. For types that are not implicitly convertible, use std::transform instead.

Self-Append: Appending a Vector to Itself

Appending a vector to itself requires care because the source iterators may be invalidated by reallocation.

cpp
1#include <vector>
2
3int main() {
4    std::vector<int> vec = {1, 2, 3};
5
6    // DANGEROUS: insert may reallocate, invalidating begin()/end() iterators
7    // vec.insert(vec.end(), vec.begin(), vec.end()); // undefined behavior
8
9    // SAFE: copy first, then insert
10    std::vector<int> copy = vec;
11    vec.insert(vec.end(), copy.begin(), copy.end());
12    // vec: {1, 2, 3, 1, 2, 3}
13}

Some standard library implementations handle self-append correctly, but the behavior is not guaranteed by the standard. Make a copy of the source data first to be safe.

Common Pitfalls

  • Using a push_back loop instead of insert. A manual loop with push_back is slower than range-based insert because insert can optimize the allocation and use bulk memory operations.
  • Forgetting reserve when using back_inserter. Without reserve, std::copy with back_inserter triggers multiple reallocations. This is slower than a single reserve followed by the copy.
  • Self-appending without a copy. Inserting a vector's own elements into itself can invalidate iterators during reallocation. Always copy the source data first.
  • Not using move semantics for expensive types. Appending vectors of strings, maps, or other complex objects should use make_move_iterator when the source is no longer needed. This avoids deep copies.
  • Assuming vec2 is empty after a move. After insert with move iterators, vec2 retains its size. Each element is in a moved-from state (valid but unspecified), but vec2 itself is not empty. Call vec2.clear() explicitly if needed.
  • Ignoring append_range in C++23 codebases. If your project targets C++23, append_range is cleaner and more expressive than the insert(end, begin, end) pattern.

Summary

  • Use vec1.insert(vec1.end(), vec2.begin(), vec2.end()) as the standard approach for appending one vector to another.
  • Call vec1.reserve(vec1.size() + vec2.size()) before insertion for guaranteed single-allocation performance.
  • Use std::make_move_iterator when the source vector's elements are expensive to copy and the source is no longer needed.
  • In C++23, vec1.append_range(vec2) provides the cleanest syntax.
  • Avoid manual push_back loops. Range-based insert enables bulk memory optimizations that element-by-element insertion cannot match.
  • Never self-append without first copying the source data, as reallocation can invalidate the source iterators.

Course illustration
Course illustration

All Rights Reserved.