Chapter 16 of 24 | C++

The standard library and ecosystem

The C++ standard library is part of the language ecosystem, not an optional collection of unrelated examples. It includes containers, algorithms, iterators, strings, streams, utilities, and more. Standard types save effort and usually make ownership and intent clearer than custom low-level code.

Containers and algorithms

#include <algorithm>
#include <vector>

std::vector<int> values{8, 3, 5, 1};
std::sort(values.begin(), values.end());
const auto found = std::find(values.begin(), values.end(), 5);

Containers own and organize elements. Iterators describe positions or ranges within a container. Algorithms operate on iterator ranges, usually expressed as a half-open range: the first iterator is included and the end iterator is not. Prefer a standard algorithm when it communicates the operation more clearly than a hand-written loop.

Common sequence containers include std::vector for contiguous resizable storage, std::array for fixed-size contiguous storage, and std::deque for efficient operations at both ends. Associative containers such as std::map keep ordered keys; std::unordered_map uses hashing and does not guarantee key order.

Useful utilities

Learn to navigate documentation

When you look up a library function, check its header, required language version, parameter and return types, complexity, iterator invalidation rules, and error behavior. Distinguish a standard guarantee from a behavior that happens to occur in one implementation.

Use std:: qualification in examples and headers. Avoid pulling the entire standard namespace into global scope. Names such as begin, end, sort, and find belong to a well-defined library vocabulary.

Choose containers by their guarantees

Container choice is about more than average speed. Consider element ordering, iterator and reference invalidation, memory locality, whether keys are unique, and whether lookup needs to be predictable. An ordered map provides sorted key iteration and logarithmic operations. A hash map can provide faster average lookup but does not keep keys ordered and may need to rehash as it grows.

A vector is often an excellent default because its contiguous memory works well with the processor cache and standard algorithms. A deque supports efficient operations at both ends. A list can preserve iterators across certain insertions, but locating an element is still linear and each node adds memory overhead. Select a specialized container when a required property matters, not from assumptions based on a single operation's name.

Understand iterator invalidation

Each container documents when insertions, removals, or reallocation invalidate iterators and references. For example, growing a vector may invalidate every iterator and reference. Inserting into an ordered map generally preserves iterators to existing elements. These are important correctness rules: an iterator that was valid before a modification may become unsafe to use afterward.

When an algorithm returns an iterator, compare it with the range's end before dereferencing it. Be careful if you modify a container while iterating: prefer algorithms designed for removal, such as the erase-remove pattern where applicable, or use the container's documented erase return value.

Optional values and errors

std::optional<T> is useful when a successful result may genuinely be absent, such as a search that found no record. Check has_value() or use the optional in a condition before accessing the contained value. It is not a substitute for every error mechanism; if failure needs details, use a result type or another interface that preserves that information.

std::variant<A, B> represents one of several alternatives. Inspect the active alternative with std::visit or a checked accessor rather than assuming which value it contains. These types make alternatives explicit and avoid loosely related flags that can contradict one another.

Complexity guarantees and real performance

Standard-library documentation often specifies complexity in terms of comparisons, operations, or average cases. Such guarantees let you reason about scaling, but they do not include every cost, such as allocations, cache misses, or expensive comparisons. A function with fewer theoretical operations can still be slower on a particular small workload. Benchmark with realistic data and the same build configuration you intend to use.

Use the library reference to verify exact semantics, not just an example copied from a search result. Check which header declares a feature, which standard introduced it, whether arguments are copied or referenced, and which iterator rules apply.

Practice

  1. Sort a vector and use std::find to check for a value.
  2. Choose a map, unordered map, or vector for a lookup problem and explain the trade-off.
  3. Look up one standard function and record its header and complexity.
  4. Use an optional result for a lookup that may not find an item, and make the caller handle absence.
  5. Compare an ordered map and an unordered map for a task that also needs sorted output.
  6. After a container change, verify whether a saved iterator remains valid.

Key idea: know the standard library well enough to reuse tested abstractions instead of recreating them.

Ranges, algorithms, and views as composable interfaces

Classic algorithms accept iterator pairs, while ranges algorithms accept range objects and can express projections and constraints more directly. Views can compose lazy transformations without allocating intermediate containers. Laziness can reduce copying, but may repeat work on each traversal and can capture references to source data. Determine whether a view is single-pass, sized, borrowed, and safe to retain before placing it in a long-lived object.

Use a projection when the algorithm should compare one part of a record without writing a custom comparator. Keep the comparison relation consistent and strict where required. For sorting, a comparator must not claim that both a < b and b < a are true, and equivalence must remain consistent. Violating algorithm preconditions is a correctness bug, not simply a way to get unusual ordering.

Allocators and polymorphic resources

Most applications should begin with the default allocator, which is efficient and well integrated with standard containers. Allocator-aware containers permit specialized allocation policies when profiling identifies a real problem. Polymorphic memory resources can separate a container's allocation interface from a runtime-selected resource, including monotonic and pool strategies.

Allocator propagation, resource lifetime, and container swap/move behavior can affect correctness. Two containers using unequal non-propagating allocators may not be interchangeable under every operation. A memory resource must outlive allocations obtained from it. Study the allocator requirements before designing a generic library abstraction around them.

Parallel algorithms and execution policies

The standard library provides execution-policy overloads for some algorithms, but selecting a policy does not guarantee a particular speedup or thread count. The algorithm's operations must be safe for the execution model, and side effects can introduce data races or ordering assumptions. Parallel work also has scheduling and synchronization costs that can exceed the computation for small ranges.

Parallelize only after identifying independent work and measuring a sequential baseline. Consult the algorithm's exception and execution-policy rules for the implementation you target. Never mutate shared state from a parallel callback without a correct synchronization strategy.

Library selection and dependency governance

A third-party library should be evaluated for API quality, maintenance, security history, supported platforms, build integration, transitive dependencies, and license. A package that compiles today may still create a long-term maintenance cost if its interface is unstable or its release process is unclear. Pin versions for reproducible builds and have a plan for updates.

Prefer standard facilities when they meet the requirement, but do not assume the standard library includes every domain abstraction. A well-maintained external parser or cryptographic library is often safer than a small hand-written substitute. The right comparison includes correctness and maintenance, not only dependency count.

Advanced exercise

  1. Rewrite an iterator algorithm as a ranges pipeline and analyze whether the pipeline is lazy and safe to retain.
  2. Use a custom memory resource for a measured allocation-heavy workload and compare total runtime and peak memory.
  3. Identify data races that would arise if a sequential algorithm callback ran concurrently.
  4. Evaluate a third-party dependency using a written checklist of maintenance, compatibility, security, and licensing concerns.
0% read