← C++, End to End

C++, End to End

Dynamic Arrays: std::vector

std::vector is a resizable array that manages its own memory. Almost every time you'd reach for a dynamically-sized array in C++, std::vector is the right tool, not a hand-rolled new/delete array. It handles growth, bounds are checkable, and it plays correctly with the rest of the standard library (algorithms, ranges, iterators).

Construction and initialization

c++ · live, editable, runnableOpen in Compiler Explorer ↗

std::vector<int> v(5, 42); and std::vector<int> v{5, 42}; do different things: the first constructs 5 elements each equal to 42, the second constructs a 2-element vector containing exactly 5 and 42. Braces prefer the initializer-list constructor when one exists and the types fit. This is a genuinely common source of confusion, worth checking any time you write vector construction with two numeric arguments.

Size vs capacity

size() is how many elements the vector actually holds. capacity() is how much memory it's currently reserved, which is often larger than size() to avoid reallocating on every single push. When capacity is exhausted, the vector allocates a new, larger block (usually growing geometrically, like doubling), copies or moves every existing element over, and frees the old block. That's an O(n) operation, but because it happens rarely as size grows, the amortized cost of a single push_back is still O(1).

c++ · live, editable, runnableOpen in Compiler Explorer ↗

Run that and watch capacity jump in steps, not one at a time. The exact growth factor isn't specified by the standard (implementations commonly use 1.5x or 2x), so don't write code that depends on the specific numbers you see, just on the general shape.

If you know roughly how many elements you'll end up with, call v.reserve(n) up front. That allocates the capacity once and avoids the reallocate-and-copy churn entirely, which matters if you're pushing thousands or millions of elements in a hot loop.

push_back vs emplace_back

push_back takes an already-constructed object (or something convertible to one) and copies or moves it into the vector. emplace_back takes the constructor arguments directly and builds the object in place, inside the vector's storage, with no separate temporary object and no copy or move of it.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

For a type this small the difference is academic. For expensive-to-move types, or types with no move constructor at all, emplace_back avoiding the extra construction can matter. It's a reasonable default habit, but not something to obsess over: correctness first, this optimization second.

Iterator invalidation

Any operation that might reallocate (push_back, insert, reserve past current capacity) can invalidate every iterator, pointer, and reference into the vector, because the whole buffer may have moved. Even operations that don't reallocate, like erase, invalidate iterators from the erased point onward, because everything after shifts down to fill the gap.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

The safe pattern for "remove things matching a condition while iterating" is to let erase tell you where the next valid iterator is, since it returns one:

c++ · live, editable, runnableOpen in Compiler Explorer ↗

For this specific pattern, the standard library's erase-remove idiom (or std::erase/std::erase_if, added in C++20 as free functions) is usually cleaner still, but understanding the manual version above is what makes the shortcut make sense rather than feel like a magic incantation.

Try it yourself: fix the reallocation bug

This function is supposed to double every element that's still below a threshold, appending the doubled value, but it has undefined behavior for the same reason as the broken loop above. Rewrite it so it's well-defined, either by not growing the vector while iterating it, or by using indices instead of iterators in a way that's still correct once growth happens.

c++
std::vector<int> growSmallValues(std::vector<int> v, int threshold) {
    for (auto it = v.begin(); it != v.end(); ++it) {
        if (*it < threshold) {
            v.push_back(*it * 2); // may reallocate and invalidate "it"
        }
    }
    return v;
}
c++ · live, editable, runnableOpen in Compiler Explorer ↗