Removing an element from a vector by index: the anatomy of std::vector::erase

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

How to erase an element from std::vector by index: looking under the hood

\n\n

Hello, 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?

\n\n

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!

\n\n

1. std::vector: what kind of beast is this?

\n\n

Before 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
    \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
\n\n

Visually, it looks like this:

\n
[0] [1] [2] [3] [4] ... [capacity-1]\n ^                        ^\n begin                   end\n
\n\n

When 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.

\n\n

2. Deleting by index: basic syntax

\n\n

The simplest way to delete an element by index is to use the erase method with an iterator:

\n\n
#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\n

Simple, right? But what if you need to delete several consecutive elements? For that, there is an overload erase(first, last):

\n\n
v.erase(v.begin() + 1, v.begin() + 3); // will delete elements at indices 1 and 2\n
\n\n

3. What's under the hood? A detailed breakdown

\n\n

Now for the interesting part. When you call v.erase(iterator), the following happens internally:

\n\n
    \n
  1. Iterator check (in debug mode) — we verify that it is valid and points to an element of the vector.
  2. \n
  3. Element shift — all elements located after the deleted one are copied (or moved, if the type supports move semantics) one position to the left.
  4. \n
  5. Size reduction — the internal size counter is decremented by 1.
  6. \n
  7. Destructor — the destructor is called for the "tail" element (which is now duplicated) (if the type is not trivial).
  8. \n
\n\n

Let's look at a simplified implementation (pseudocode):

\n\n
template<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\n

Notice std::move — modern C++ tries to avoid

Blogs

Book Recommendations