Phase 3 · Generic Programming & the Standard LibraryModule 15~56 min read

Standard Containers & Adaptors

Select standard sequence, associative, unordered, and adaptor containers from their contracts and complexity guarantees.

What you'll learn

The standard containers encode data structures, ownership, iteration, and complexity contracts. Choosing well begins with access and mutation patterns—not with memorizing names. You will work across sequences, associative containers, unordered lookup, and constrained adaptors.

By the end, you'll be able to:

  • Select a container from ordering, lookup, insertion, and locality requirements
  • Use ordered and unordered associative containers safely
  • Explain complexity, iterator invalidation, and hashing contracts
  • Apply stack, queue, and priority_queue adaptors

Choose from operations

Start with the default std::vector. Move away only when a measured or structural requirement demands stable node addresses, cheap insertion at both ends, key-based lookup, or ordered traversal. Big-O describes growth, but locality, allocation count, and constants still shape real performance.

NeedTypical first choiceWhy
Contiguous growable sequencestd::vectorLocality and random access
Fast growth at both endsstd::dequeSegmented sequence with end operations
Sorted unique keysstd::map / std::setLogarithmic ordered operations
Fast average key lookupstd::unordered_map / setHash table
Highest-priority itemstd::priority_queueHeap-backed adaptor

Key idea

A linked list is not automatically faster for insertion: reaching the position is linear, each node allocates, and poor locality can dominate. Choose it only when its stability and splice behavior matter.

Sequence containers

Sequence containers arrange elements by position. vector and arrayare contiguous; deque is segmented; list andforward_list are node-based. All own their elements and clean them up automatically.

deque.cpp
#include <deque>
#include <iostream>
#include <string>

int main() {
    std::deque<std::string> work;
    work.push_back("render");
    work.push_front("parse");
    work.emplace_back("publish");

    while (!work.empty()) {
        std::cout << work.front() << '\n';
        work.pop_front();
    }
}
  • vector provides constant-time random access and amortized constant push_back
  • deque provides constant-time access plus efficient insertion at both ends
  • list provides stable iterators except to erased elements and constant-time splice
  • forward_list is a minimal singly linked list without size()

Ordered associative containers

map stores key-value pairs and set stores keys. Their elements are ordered by a strict weak ordering, with logarithmic lookup and insertion. Themulti variants permit equivalent keys. A map's key is const inside each element because changing it in place would break ordering.

word_counts.cpp
#include <iostream>
#include <map>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> words{"map", "vector", "map", "set"};
    std::map<std::string, int> counts;
    for (const auto& word : words) ++counts[word];

    for (const auto& [word, count] : counts) {
        std::cout << word << ": " << count << '\n';
    }
}

Note

operator[] inserts a default value when a map key is missing. Use find, contains, or at when lookup must not mutate the container.

Unordered containers and hashing

Unordered containers place elements into buckets based on a hash. Average lookup is constant time, but worst-case lookup is linear. If two keys compare equal, their hash values must match. Iteration order is unspecified and may change after rehashing.

inventory.cpp
#include <iostream>
#include <string>
#include <unordered_map>

int main() {
    std::unordered_map<std::string, int> stock{
        {"keyboard", 8}, {"mouse", 12}
    };

    if (auto found{stock.find("mouse")}; found != stock.end()) {
        found->second -= 1;
    }
    std::cout << stock.at("mouse") << '\n';
}

Watch out

Never serialize or test unordered-container iteration order. If deterministic output matters, sort a view of the keys or choose an ordered container.

Container adaptors

An adaptor restricts an underlying container to a focused interface. stackexposes last-in-first-out operations, queue exposes first-in-first-out operations, and priority_queue exposes the greatest element according to its comparison.

priority_queue.cpp
#include <iostream>
#include <queue>
#include <string>

struct Job { int priority; std::string name; };
struct LowerPriority {
    bool operator()(const Job& left, const Job& right) const {
        return left.priority < right.priority;
    }
};

int main() {
    std::priority_queue<Job, std::vector<Job>, LowerPriority> jobs;
    jobs.push({2, "index"});
    jobs.push({5, "restore"});
    jobs.push({1, "cleanup"});
    std::cout << jobs.top().name << '\n';
}

Complexity and invalidation

Complexity guarantees describe how operations scale. Invalidation rules describe whether existing iterators, references, and pointers remain usable after mutation. These are part of correctness, not merely optimization, and differ across container families.

Container familyTypical insertionStability summary
vectorAmortized O(1) at endReallocation invalidates all; erase shifts later elements
dequeO(1) at endsInsertion can invalidate iterators; references have nuanced guarantees
listO(1) at a known positionOnly erased element invalidated
map/setO(log n)Only erased element invalidated
unorderedAverage O(1)Rehash invalidates iterators; references remain to elements

Tip

Read the exact operation contract before retaining an iterator across mutation. “Node-based” and “contiguous” are useful mental models, but the specification is the authority.

Recap & quick check

Key takeaways

  • Start with vector and switch only for a concrete access, mutation, ordering, or stability requirement.
  • Ordered associative containers provide logarithmic operations and deterministic key order.
  • Unordered containers require equal keys to hash equally and do not promise iteration order.
  • Adaptors deliberately expose only stack, queue, or heap-shaped operations.
  • Complexity and invalidation guarantees are correctness-relevant parts of a container contract.

Quick check

1. Which is the usual default growable sequence?

2. What does map::operator[] do for a missing key?

3. What must be true when two unordered-map keys compare equal?

4. Which adaptor exposes the greatest-priority element?

Next: Module 16 — Iterators, Algorithms & Ranges, where containers become inputs to a shared vocabulary of operations.