Skip to content

Vector — Dynamic Array, Capacity, emplace_back, shrink_to_fit

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Vector. We cover key concepts, practical examples, and best practices to help you master this topic.

C++ std::vector is a dynamically-resizable array with contiguous storage, providing amortized constant-time push_back, random access, and fine-grained control over capacity and memory allocation.

What You'll Learn

You will create and manipulate vectors, understand capacity and size management including reserve, resize, and shrink_to_fit, use emplace_back and emplace for efficient in-place construction, manage Iterator invalidation during insertion and erasure, and implement the erase-remove idiom for element removal.

Why It Matters

std::vector is the most widely used standard library container. Its contiguous memory layout matches the processor's cache hierarchy, making it faster than linked lists in most scenarios. Understanding vector's growth Strategy, capacity management, and emplacement is essential for writing efficient C++ code.

Learning Path

graph LR
    A["29: STL Overview"] --> B["30: Vector"]
    B --> C["31: Deque, List, Forward List"]
    C --> D["32: Set & Multiset"]
    style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style D fill:#4a90d9,stroke:#2c5f8a,color:#fff

Creating and Initializing

#include <iostream>
#include <vector>

int main() {
    // Default constructor: empty vector
    std::vector<int> v1;
    
    // Size + value constructor
    std::vector<int> v2(5, 10);  // {10, 10, 10, 10, 10}
    
    // Initializer list (C++11)
    std::vector<int> v3 = {1, 2, 3, 4, 5};
    
    // Copy constructor
    std::vector<int> v4 = v3;
    
    // Range constructor (from iterators)
    std::vector<int> v5(v3.begin() + 1, v3.end() - 1);  // {2, 3, 4}
    
    for (int x : v5) std::cout << x << " ";
    std::cout << "\n";
}

Accessing Elements

#include <iostream>
#include <vector>
#include <stdexcept>

int main() {
    std::vector<int> vec = {10, 20, 30, 40, 50};
    
    // operator[] — unchecked, fast
    std::cout << vec[2] << "\n";  // 30
    
    // at() — bounds-checked (throws std::out_of_range)
    try {
        std::cout << vec.at(10) << "\n";
    } catch (const std::out_of_range& e) {
        std::cout << "Out of range: " << e.what() << "\n";
    }
    
    // front() and back()
    std::cout << vec.front() << " " << vec.back() << "\n";  // 10 50
    
    // data() — raw pointer to underlying array
    int* raw = vec.data();
    raw[0] = 99;
    std::cout << vec[0] << "\n";  // 99
}

Size vs Capacity

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec;
    
    std::cout << "size: " << vec.size() << ", capacity: " << vec.capacity() << "\n";
    
    vec.push_back(1);
    std::cout << "After push_back: size=" << vec.size()
              << ", capacity=" << vec.capacity() << "\n";
    
    // Reserve space to avoid reallocations
    vec.reserve(100);
    std::cout << "After reserve(100): size=" << vec.size()
              << ", capacity=" << vec.capacity() << "\n";
    
    // Shrink to fit (may reallocate)
    vec.shrink_to_fit();
    std::cout << "After shrink_to_fit: size=" << vec.size()
              << ", capacity=" << vec.capacity() << "\n";
    
    // resize: changes size, default-inserting or removing elements
    vec.resize(5);
    std::cout << "After resize(5): size=" << vec.size() << "\n";
    for (int x : vec) std::cout << x << " ";
    std::cout << "\n";
}

Growth Strategy

Different implementations use different growth factors:

  • GCC (libstdc++): 2x
  • Clang (libc++): 2x
  • MSVC: 1.5x
#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec;
    
    for (int i = 0; i < 20; ++i) {
        vec.push_back(i);
        std::cout << "size=" << vec.size() << ", capacity=" << vec.capacity() << "\n";
    }
}

Expected output (with 2x growth factor):

size=1, capacity=1
size=2, capacity=2
size=3, capacity=4
size=4, capacity=4
size=5, capacity=8
...

emplace_back and emplace

#include <iostream>
#include <vector>
#include <string>

struct Point {
    int x, y;
    Point(int a, int b) : x(a), y(b) {
        std::cout << "Point(" << x << "," << y << ")\n";
    }
};

int main() {
    std::vector<Point> points;
    
    // push_back: creates temporary, then moves/copies
    points.push_back(Point(1, 2));
    
    // emplace_back: constructs in place (more efficient)
    points.emplace_back(3, 4);  // passes arguments directly to constructor
    
    // emplace at position
    points.emplace(points.begin() + 1, 5, 6);
    
    // With strings
    std::vector<std::string> strings;
    strings.emplace_back(10, 'a');  // constructs "aaaaaaaaaa" in place
    strings.push_back("hello");
    
    for (const auto& s : strings) {
        std::cout << s << "\n";
    }
}

emplace_back forwards its arguments directly to the constructor, avoiding the temporary object created by push_back.

Inserting and Erasing

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    
    // Insert at position
    vec.insert(vec.begin() + 2, 99);  // {1, 2, 99, 3, 4, 5}
    
    // Insert multiple copies
    vec.insert(vec.end(), 3, 100);  // {..., 100, 100, 100}
    
    // Insert from another range
    std::vector<int> more = {200, 201};
    vec.insert(vec.end(), more.begin(), more.end());
    
    // Erase single element
    vec.erase(vec.begin() + 1);  // removes element at index 1
    
    // Erase range
    vec.erase(vec.begin() + 2, vec.begin() + 4);
    
    // Clear all
    vec.clear();
}

The Erase-Remove Idiom

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {1, 2, 3, 2, 4, 2, 5};
    
    // Remove all occurrences of 2
    auto newEnd = std::remove(vec.begin(), vec.end(), 2);
    // remove shifts non-removed elements to the front, returns new logical end
    
    vec.erase(newEnd, vec.end());  // actually shrink the vector
    
    for (int x : vec) std::cout << x << " ";
    std::cout << "\n";  // 1 3 4 5
    
    // Alternative: single line
    // vec.erase(std::remove(vec.begin(), vec.end(), 2), vec.end());
    
    // Conditional remove
    vec.erase(std::remove_if(vec.begin(), vec.end(),
                [](int x) { return x % 2 == 0; }),
              vec.end());
    
    for (int x : vec) std::cout << x << " ";
    std::cout << "\n";  // 1 3 5
}

Common Mistakes

Mistake 1: Not Reserving

std::vector<int> v;
for (int i = 0; i < 100000; ++i) v.push_back(i);  // 17 reallocations

Always reserve if you know the size in advance.

Mistake 2: Using resize Instead of reserve

v.resize(1000);  // creates 1000 default-constructed elements

Use reserve to allocate memory without creating elements.

Mistake 3: Storing bool in vector

std::vector<bool> is a bitset specialization, not a container of bools. It does not meet container requirements. Use std::deque<bool> or std::vector<char> for real bools.

Mistake 4: Iterator Invalidation After Insertion/Erase

After inserting or erasing, all iterators at or after the modification point are invalidated. Use the returned iterator.

Mistake 5: Passing Vector by Value

void process(std::vector<int> v) { ... }  // copies entire vector

Pass by const& for read-only, or by && for move semantics.

Mistake 6: Ignoring the Small Vector Optimization

std::vector always heap-allocates. For small, fixed-size arrays, prefer std::array or std::string for short strings (small string optimization).

Practice Questions

  1. What is the difference between size() and capacity()?
  2. When should you use emplace_back instead of push_back?
  3. What is the erase-remove idiom and why is it needed?
  4. What happens to iterators after push_back causes a reallocation?
  5. Why is std::vector<bool> special and often problematic?

Challenge

Implement a function compact that takes a vector of optional values (std::vector<std::optional<int>>) and returns a vector containing only the present values, preserving order.

FAQ

Why is `vector` faster than `list` for most use cases?

Vector's contiguous memory provides excellent cache locality. Even operations where list has better theoretical complexity (insertion in the middle) are often faster with vector because of cache effects.

Does `shrink_to_fit` guarantee that capacity equals size?

No, it is a non-binding request. The implementation may ignore it. Use vector<int>(v).swap(v) to force shrinking.

Can I use `vector` with move-only types?

Yes. std::vector<std::unique_ptr<int>> works because unique_ptr is movable. The vector will use move operations during reallocation.

What is the cost of `push_back`?

Amortized O(1). Most calls are O(1), but occasional reallocations are O(n). The amortized analysis guarantees that n push_backs are O(n) total.

Is `vector` thread-safe for reads?

Multiple threads can read from the same vector simultaneously only if no thread is modifying it.

What is the difference between `cbegin()` and `begin()`?

cbegin() returns a const iterator that cannot modify elements. Use it when you only need read access.

Mini Project

Build a growable matrix using vector of vector:

#include <iostream>
#include <vector>

class Matrix {
private:
    std::vector<std::vector<double>> data_;
    
public:
    Matrix(size_t rows, size_t cols)
        : data_(rows, std::vector<double>(cols, 0.0)) {}
    
    double& operator()(size_t r, size_t c) {
        return data_[r][c];
    }
    
    const double& operator()(size_t r, size_t c) const {
        return data_[r][c];
    }
    
    size_t rows() const { return data_.size(); }
    size_t cols() const { return data_.empty() ? 0 : data_[0].size(); }
    
    Matrix operator*(const Matrix& other) const {
        Matrix result(rows(), other.cols());
        for (size_t i = 0; i < rows(); ++i) {
            for (size_t k = 0; k < cols(); ++k) {
                for (size_t j = 0; j < other.cols(); ++j) {
                    result(i, j) += data_[i][k] * other(k, j);
                }
            }
        }
        return result;
    }
    
    void print() const {
        for (size_t i = 0; i < rows(); ++i) {
            for (size_t j = 0; j < cols(); ++j) {
                std::cout << data_[i][j] << "\t";
            }
            std::cout << "\n";
        }
    }
};

int main() {
    Matrix a(2, 3);
    a(0, 0) = 1; a(0, 1) = 2; a(0, 2) = 3;
    a(1, 0) = 4; a(1, 1) = 5; a(1, 2) = 6;
    
    Matrix b(3, 2);
    b(0, 0) = 7; b(0, 1) = 8;
    b(1, 0) = 9; b(1, 1) = 10;
    b(2, 0) = 11; b(2, 1) = 12;
    
    Matrix c = a * b;
    c.print();
}

What's Next

Vector is your default container. The next lesson covers deque, list, and forward_list: their performance characteristics and when they outperform vector.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro