The standard library and smart pointers

Containers, algorithms and ownership: choose the right container and make leaks structurally impossible.

Containers

ContainerBacking structureUse it for
std::vectorContiguous arrayThe default choice: iteration, indexing, cache-friendly storage
std::arrayFixed-size arrayA size known at compile time, with no heap allocation
std::stringOwned character bufferText: it knows its length, unlike a char*
std::dequeChunked arrayFast push and pop at both ends
std::map / std::setBalanced treeOrdered keys, range queries, O(log n) lookup
std::unordered_mapHash tableAverage O(1) lookup with no ordering requirement
std::span (C++20)Non-owning viewPassing a range into a function without copying
#include <map>
#include <unordered_map>
#include <vector>

std::vector<int> v{ 5, 3, 9 };
v.push_back(7);
v.reserve(100);                       // one allocation instead of several
int first = v.front();
v.erase(v.begin());                   // erasing shifts the elements after it

std::map<std::string, int> ordered;
ordered["ada"] = 36;                  // inserts on first use
ordered.insert_or_assign("grace", 45);
for (const auto &[key, value] : ordered) {          // structured binding
    std::cout << key << " " << value << "\n";
}

std::unordered_map<std::string, int> fast;   // same interface, no ordering

Algorithms and lambdas

The algorithms in <algorithm> work on any pair of iterators, so one call serves a vector, an array or a map.

#include <algorithm>
#include <numeric>

std::sort(v.begin(), v.end());
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });   // descending

auto it = std::find_if(v.begin(), v.end(),
                       [threshold = 5](int x) { return x > threshold; });

auto total = std::accumulate(v.begin(), v.end(), 0);

// capture by reference to update something outside the lambda
int seen = 0;
std::for_each(v.begin(), v.end(), [&seen](int &x) { x += ++seen; });

// erase-remove: an algorithm cannot resize the container by itself
v.erase(std::remove_if(v.begin(), v.end(),
                       [](int x) { return x % 2 == 0; }),
        v.end());
  • A lambda's capture list decides what it may touch: [x] copies, [&x] refers, and [&] captures everything by reference — which risks a dangling reference if the lambda outlives the scope.
  • Algorithms never change a container's size, hence the erase-remove idiom.
  • A comparator must be a strict weak ordering: using <= instead of < is undefined behaviour.
  • std::ranges in C++20 lets you write std::ranges::sort(v) without naming iterators.

Smart pointers

#include <memory>

// unique ownership: exactly one owner, released when it goes out of scope
auto buf = std::make_unique<Buffer>(1024);
buf->fill();

// shared ownership: reference counted, released when the last owner goes
std::shared_ptr<Config> cfg = std::make_shared<Config>("app.conf");
auto copy = cfg;                       // the count is now 2
std::weak_ptr<Config> observer = cfg;  // does not keep it alive; check with lock()

if (auto locked = observer.lock()) {
    locked->reload();
}

// a factory that returns unique ownership
std::unique_ptr<Shape> make(const std::string &kind) {
    if (kind == "circle") return std::make_unique<Circle>(1.0);
    return std::make_unique<Square>(2.0);
}

std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(make("circle"));      // moves the pointer, does not copy it
⚠️
Never build two shared_ptrs from the same raw pointer, and prefer std::make_unique and std::make_shared over new: they are exception-safe and cannot get the ownership count wrong. Reach for shared ownership only when ownership genuinely is shared, because the count has a cost.

FAQ

When is a raw pointer still appropriate?
For non-owning observation, usually as a parameter. The problem is owning raw pointers: any new you write should normally be inside a smart pointer, and a class should not need a destructor to delete members.
How does <code>shared_ptr</code> create a cycle?
Two objects holding shared_ptrs to each other keep both counts above zero, so neither is ever released. Break the cycle by making one direction a weak_ptr.

Classes, constructors and RAII Modern C++ basics

Last refreshed 2026-09-18.