Introduction: What the Documentation Doesn't Tell You
Imagine you're writing a high-load server or a data processing algorithm. You have two std::vector<int>s, and you need to merge them into one. It seems simple enough, right? Just add them. But C++ doesn't forgive a superficial approach. One wrong method, and you get unnecessary copies, performance drops, or even undefined behavior.
The question "Appending a vector to a vector" on Stack Overflow has gathered thousands of views for a reason. Even experienced developers sometimes confuse push_back with insert or forget about move semantics. In this article, we'll look under the compiler's hood and understand how adding one vector to another actually works. You'll learn why std::copy can be faster than insert, and when move semantics can save your project.
The Most Obvious (and Dangerous) Way: push_back in a Loop
Beginners often write code like this:
std::vector<int> dest = {1, 2, 3};std::vector<int> src = {4, 5, 6};
for (size_t i = 0; i < src.size(); ++i) { dest.push_back(src[i]);}What's happening here? On each iteration, push_back checks if enough memory has been allocated. If not, a reallocation occurs: the old array is copied to a new memory area, and the old one is deleted. With three elements, there might be 2–3 reallocations. For small vectors, this is unnoticeable, but with thousands of elements, it's a disaster.
The problem: O(n) checks and potential copies of the entire array. In C++17 and later, the compiler may optimize this, but relying on it is risky.
What Happens Under the Hood of push_back?
- Check
size() < capacity(). - If not, call
reallocate(new_capacity). - Copy (or move) existing elements.
- Delete the old buffer.
- Copy the new element.
Conclusion: never use a loop to add an entire vector, unless you are doing it intentionally for a small number of elements.
The Right Way: std::vector::insert
The standard library provides an elegant solution:
std::vector<int> dest = {1, 2, 3};std::vector<int> src = {4, 5, 6};
dest.insert(dest.end(), src.begin(), src.end());A one-liner! But what's hidden behind this call? insert does the following:
- Calculates the required amount of memory:
dest.size() + src.size(). - If the current capacity is insufficient, it allocates exactly what is needed (or with a margin, depending on the implementation).
- Shifts existing elements (if inserting not at the end).
- Copies (or moves) new elements from
src.
Important: insert performs one reallocation (or none if there is enough space). This is already better than a loop, but there is still copying of elements from src.
Performance of insert
If src is no longer needed after insertion, you can move its elements using std::make_move_iterator:
dest.insert(dest.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end()));This turns copying into moving. For std::vector<int> there is no difference (int is a trivial type), but for std::vector<std::string> or complex objects, moving is cheap compared to copying.
Alternative: std::copy with back_inserter
Some developers prefer STL algorithms:
Blogs
Book Recommendations
Read also
Clean Code in C++: Principles and Practical Examples
Docker and Java: Advanced Application Containerization
Learn JavaScript Online: Practice, Theory & Coding Tasks
JavaScript from Scratch: Where to Start in 2025 — A Beginner's Guide
Monorepository with TypeScript: Code Organization and Best Practices