How to erase an element from std::vector by index: looking under the hood
\n\nHello, colleague! If you've ever written in C++ and worked with dynamic arrays, you've probably encountered the question: "How do I erase an element from std::vector<> by index?" — this is one of the most popular queries on Stack Overflow, and for good reason. At first glance, the task seems trivial: call erase and you're done. But what actually happens in memory? Why is deleting from the middle of a vector expensive? And how do you avoid pitfalls with iterators?
In this article, we won't just show you how to write v.erase(v.begin() + index). We'll break down how this operation works under the hood, what data structures are involved, and how to choose the optimal deletion method. Let's go!
1. std::vector: what kind of beast is this?
\n\nBefore deleting, let's recall how std::vector is structured. It is a dynamic array that stores elements in a contiguous memory region. It has three key pointers (or iterators):
- \n
begin— the start of the allocated memory; \n end— the end of the used memory (past the last element); \n capacity— the end of the allocated memory (the boundary up to which it can grow without reallocation). \n
Visually, it looks like this:
\n[0] [1] [2] [3] [4] ... [capacity-1]\n ^ ^\n begin end\n\n\nWhen you delete an element from the middle, the vector doesn't just "throw it out" — it physically shifts all subsequent elements to the left. This is the main "pain" of the erase operation.
2. Deleting by index: basic syntax
\n\nThe simplest way to delete an element by index is to use the erase method with an iterator:
#include <vector>\n#include <iostream>\n\nint main() {\n std::vector<int> v = {10, 20, 30, 40, 50};\n int index = 2; // we want to delete the third element (30)\n\n // Check that the index is within bounds\n if (index < v.size()) {\n v.erase(v.begin() + index);\n }\n\n for (int x : v) std::cout << x << " ";\n // Output: 10 20 40 50\n return 0;\n}\n\n\nSimple, right? But what if you need to delete several consecutive elements? For that, there is an overload erase(first, last):
v.erase(v.begin() + 1, v.begin() + 3); // will delete elements at indices 1 and 2\n\n\n3. What's under the hood? A detailed breakdown
\n\nNow for the interesting part. When you call v.erase(iterator), the following happens internally:
- \n
- Iterator check (in debug mode) — we verify that it is valid and points to an element of the vector. \n
- Element shift — all elements located after the deleted one are copied (or moved, if the type supports move semantics) one position to the left. \n
- Size reduction — the internal
sizecounter is decremented by 1. \n - Destructor — the destructor is called for the "tail" element (which is now duplicated) (if the type is not trivial). \n
Let's look at a simplified implementation (pseudocode):
\n\ntemplate<typename T>\ntypename std::vector<T>::iterator\nvector<T>::erase(iterator pos) {\n // 1. Shift all elements after pos one position to the left\n for (auto it = pos; it != end() - 1; ++it) {\n *it = std::move(*(it + 1));\n }\n // 2. Decrease size\n --size;\n // 3. Call the destructor for the last element\n allocator.destroy(end()); // end() now points to the former last element\n return pos; // return an iterator to the element following the deleted one\n}\n\n\nNotice std::move — modern C++ tries to avoid