Modern C++ containers provide safe, expressive abstractions for storing and managing data. This chapter compares vector, array, deque, list, and associative containers, showing how memory layout, access patterns, ordering, iterator stability, and allocation requirements inform sound container selection.
Iterators provide a common interface between containers and STL algorithms. Their categories define the operations they support, while range-based loops, standard algorithms, and lambdas enable concise traversal, searching, sorting, transformation, removal, accumulation, and customized behavior.
Used effectively, these tools replace error-prone manual memory management and hand-written loops with reusable, predictable operations. Practical engine applications include vertex and component storage, registries, lookup tables, caches, and fixed-size buffers, with particular attention to the erase–remove idiom, cache-efficient design, and the avoidance of unsuitable containers and raw allocation.
Containers, iterators, and algorithms are foundational to modern C++. They replace manual memory management, raw arrays, hand-written loops, and fragile data handling with abstractions that are safer, clearer, and more efficient. Mastery of these tools is essential to writing clean, production-quality C++.
Rather than treating the STL (Standard Library) as a loose collection of utilities, this chapter presents it as a cohesive system founded on value semantics, zero-cost abstractions, and predictable behavior.
Containers address three fundamental requirements:
In modern C++, standard containers should replace raw arrays in nearly every practical scenario.
The STL provides several container types, each designed for a distinct set of use cases.
Example:
std::vector<Vec3> points;
points.push_back({1,2,3});
std::array<T, N> — Fixed Size Array
Example:
std::array<float, 3> color = {1.0f, 0.5f, 0.2f};
std::deque<T> — Double Ended Queue
std::list<T> — Linked List
std::map<K, V> — stores ordered key-value pairsstd::unordered_map<K, V> — stores hashed key-value pairsstd::set<T> — stores ordered unique valuesstd::unordered_set<T> — stores hashed unique valuesGame engines commonly use hash maps (unordered_map) for registries, rapid lookups, and resource tables.
Modern C++ requires
std::vector when:
std::unordered_map when:
std::array when:
std::list unless:
Container selection is therefore an architectural decision, not merely an implementation detail.
Iterators are generalized pointer-like abstractions.
They enable the same algorithms to operate consistently across different container types.
Example:
std::vector<int> v = {1,2,3};
for (auto it = v.begin(); it != v.end(); ++it)
{
std::cout << *it;
}
For straightforward traversal, modern C++ generally favors range-based loops:
for (int x : v)
{
std::cout << x;
}
Nevertheless, a strong understanding of iterators remains essential when working with algorithms.
A container’s capabilities determine the category of iterator it provides:
Reads values in sequence.
Writes values in sequence.
Moves only forward and supports repeated passes.
Moves both forward and backward.
Supports direct movement to any position, as provided by vector and array.
Random-access iterators support the STL’s most powerful algorithms.
The <algorithm> library provides hundreds of functions that operate over iterator ranges.
Examples:
auto it = std::find(v.begin(), v.end(), 42);
std::sort(v.begin(), v.end());
std::transform(v.begin(), v.end(), v.begin(),
[](int x){ return x * 2; });
v.erase(std::remove(v.begin(), v.end(), 0), v.end());
int sum = std::accumulate(v.begin(), v.end(), 0);
Algorithms replace repetitive manual loops with concise, reusable, and intention-revealing operations.
The erase–remove idiom is one of the STL’s most important patterns:
v.erase(std::remove(v.begin(), v.end(), value), v.end());
Why?
remove reorders the range and shifts retained elements forwarderase shortens the container by removing the trailing elementsThis idiom is fundamental to clear and correct container manipulation.
Lambdas make algorithmic code both concise and expressive:
std::sort(v.begin(), v.end(),
[](const Item& a, const Item& b)
{
return a.score < b.score;
});
They provide customized behavior without the overhead of separate function objects or boilerplate code.
Containers are integral to virtually every production engine system:
A thorough understanding of containers is essential to designing modular, efficient, and maintainable systems.
These are some common mistakes which we should try to avoid in our code.
Modern C++ avoids these pitfalls through deliberate, context-aware container selection.
This chapter covered:
Together, these concepts establish a strong foundation for the next chapter on error handling, exceptions, and contracts.