Concepts / Sequential Containers in C++

Sequential Containers in C++

Standard library containers are organized into families, and the family tells you whether elements are reached by position, by key, or through hashed lookup.

  • Programming

Deciding What Kind of Collection You Need

Before you write a program that stores a group of values, you have to decide what kind of collection you need. Should the items stay in the order you added them? Should you look an item up by a name or label? Should duplicates be allowed? The C++ standard library answers these questions with a set of ready-made container types, so you do not have to build each storage structure yourself. The library is designed around the idea that you pick the container that best fits the needs of your application.

This article walks through the containers by asking what each one is good at. The goal is to turn a list of names into a set of choices you can reason about. Keep three questions in mind as you read: does the container keep elements in a sequence, does it store elements under keys, and does it use hashed lookup?

Three Families of Standard Containers

The standard containers fall into families, and the family tells you how elements are arranged and how you reach them. Sequential containers keep elements in a sequence, so position is the main way to think about them. Associative containers store elements together with keys, so you reach a stored item by its key. Unordered containers are also keyed, but they are built on hash tables and are optimized for lookup, which is often done with a string key.

Standard containersSequentialreached by positionvector<T>Associativereached by keylist<T>Unorderedhashed key lookupforward_list<T>deque<T>set<T>map<K,V>multiset<T>multimap<K,V>unordered_map<K,V>unordered_multimap<K,V>unordered_set<T>unordered_multiset<T>familyfamilyfamily
Which standard containers belong to the sequential, associative, and unordered families?

Read the diagram from top to bottom. The root is the idea of a standard container, and the three families sit beneath it. Each family then contains the specific containers the source names. The unordered family mirrors the associative family in its set-or-map and single-or-multiple distinctions, but its lookup mechanism is different. The rest of this article explains each family in turn, starting with the one whose elements are reached by position.

Tracing a Lookup by Position

Trace what happens when you want an element from a sequential container. A sequential container arranges its elements in a line, so you think about where an element sits in that line. Suppose the container holds four scores in order. To get the score that sits at position 2, you name that position. You do not search the values to find the one you want.

position 090position 175position 260position 388nextnextnext
When you look up data in a sequential container, what identifies the element you want?

The highlighted cell is the one the lookup asks for. Its position, 2, identifies it. The values in the picture are invented for illustration. The source does not give example scores. The point of the picture is that a position-based lookup answers the question which place in the line, not which value.

Sequential Containers and Their Links

The source names four sequential containers, and each one differs in how it is built and which ends are easy to reach. The vector<T> is described as a variable-size vector, so it can grow or shrink as you add or remove elements. The list<T> is a doubly-linked list, so each element knows about the element before it and the element after it. The forward_list<T> is a singly-linked list, which links each element only to the next one. The deque<T> is a double-ended queue, so it is built for working at both of its ends.

ContainerSource descriptionWhat to notice
vector<T>Variable-size vectorGrows or shrinks as elements are added or removed
list<T>Doubly-linked listEach element links to the element before it and the element after it
forward_list<T>Singly-linked listEach element links only to the next element
deque<T>Double-ended queueBuilt for working at both ends

The four sequential containers named in the source, with the distinguishing detail from each description

The differences in linking and in the ends that are easy to reach are the reasons you would choose one over another. The source does not compare their speed or memory use, so this article does not make those claims. Choose by asking which kind of growth or end access your program needs.

cpp
Output
second holds 75
Notes on the code
  • scores is a sequential container, so its elements are reached by position
  • push_back adds each new value at the end of the line
  • scores[1] names position 1, which is the second element

Associative Containers Reached Through Keys

An associative container stores elements that are reached through keys. Instead of asking for the element at a position, you give the container a key and receive the stored item for that key. The source describes set<T> as a set that holds just a key with no separate value. The map<K,V> is an associative array that pairs a key of type K with a value of type V.

ages mapAna31Ben27key to valuekey to value
In a map, what does a key lead you to?

The keys and values in this picture are a generated illustration of the idea, not data taken from the source. The picture shows that you find a value by its key, and that the key itself is the handle you use to store and retrieve data.

cpp
Output
anaAge holds 31
Notes on the code
  • The map pairs each std::string key with an int value
  • Writing ages["Ana"] stores a value under the key Ana
  • Reading ages["Ana"] finds the value by its key, not by a position

The multi-versions relax one rule each. A multiset<T> lets a value occur many times, and a multimap<K,V> lets a key occur many times. So if the same key may appear more than once, a multimap is the family member the source points you to, while a plain map keeps one value per key.

Unordered Containers and Hashed Lookup

The unordered containers are optimized for lookup with a key, and the source notes that a key is often a string. They are implemented using hash tables. The source lists four of them: unordered_map<K,V>, unordered_multimap<K,V>, unordered_set<T>, and unordered_multiset<T>. Each one mirrors a container from the associative family, with the same set-or-map and single-or-multiple distinctions, but the lookup mechanism is a hashed one.

The point to hold onto is the purpose. When fast lookup by key is the main job, the unordered versions are the ones the source singles out. When you need the stored order or the sorted keys, the associative family is the one to consider, since the unordered family is described by its lookup mechanism rather than by ordering.

Container Adaptors and Container-Like Types

The standard library also provides container adaptors, named queue<T>, stack<T>, and priority_queue<T>. An adaptor is a class that provides a different interface to a container. It does not present the operations of a general-purpose collection. Instead, it presents a narrower set of operations suited to a particular access pattern. The source tells you the adaptors exist and gives their names, and it advises you to look them up when you need them.

Some types in the standard library behave like containers without fitting neatly into the three families or the adaptors. The source gives two examples. The array<T,N> is a fixed-size array, so its size is set as part of its type rather than growing as elements are added. The bitset<N> is a fixed-size collection of bits. Both are described as more specialized container-like types, and they are useful to know about so that you do not mistake them for the variable-size containers above.

KindExamples named in the sourceDefining idea
Ordinary containersvector, list, forward_list, deque, set, map, and their unordered and multi versionsStore elements that you reach by position, key, or hashed key lookup
Container adaptorsqueue, stack, priority_queueProvide a different, narrower interface to a container
Container-like typesarray, bitsetBehave like containers but are fixed-size and more specialized

How ordinary containers differ from adaptors and container-like types

Tracing a Container Choice

Worked example

Choosing a container from three questions

A program must store student names and look up a student's record by name. Duplicate names should not be needed, and the program does not need the names kept in any particular order. Which family fits best?

  1. 1
    Ask how the data is reached

    The program looks up records by name, which is a key. A position-based sequential container is not the natural choice when the key is the main access path.

  2. 2
    Ask whether duplicates are allowed

    The problem says duplicate names are not needed. That points to a set or map rather than a multiset or multimap, since those relax the one-occurrence rule.

  3. 3
    Ask whether ordering matters

    The problem says the names need no particular order. The unordered family is the one the source singles out when fast lookup by key is the main job.

  4. 4
    Choose the container

    An unordered_map<K,V> with a string key fits. The record is the value V, and the name is the key K. This matches the source's note that unordered containers are often used with string keys.

Result

An unordered_map from a string name to a record is the best fit, because lookup by key is the main job, duplicates are not needed, and no order is required.

Practice Choosing a Container

MEDIUM

A program must keep the order in which customers arrive and must process the customer at the front of that line first. Which family or adaptor would you consider, and what question did you use to decide?

Hints
  • Ask whether the main way to reach an element is by position or by key.
  • Think about whether the program needs a narrower interface that offers only the operations for one access pattern.
  • The queue<T> adaptor is one of the three adaptors the source names.
EASY

Name the family of each container: vector<T>, set<T>, and unordered_map<K,V>. Then say whether a multiset<T> would change the family of the set.

Hints
  • vector is in the sequential family because it is reached by position.
  • set and unordered_map are keyed, so compare their lookup mechanisms.
  • A multiset relaxes a rule about duplicates, not the family it belongs to.

Mistakes Learners Make with Container Families

  • Common mistake

    Treating every container as a list of values reached by position

    What it looks like

    Looking up a student's record in a vector by searching for the name, when the record is naturally reached by a name key

    Why it fails

    Sequential containers are arranged by position, while associative and unordered containers are reached through keys

    Do this instead

    Ask first whether the main access path is a position or a key, and pick the family that matches

  • Common mistake

    Assuming a multimap and a map differ in family

    What it looks like

    Choosing a map when the same key must appear several times

    Why it fails

    The multi-versions stay in the associative family and relax only the rule that a key occurs once

    Do this instead

    Keep the family and change only the duplicate rule, choosing multimap when keys repeat

  • Common mistake

    Assuming unordered containers behave exactly like associative containers

    What it looks like

    Expecting an unordered_map to keep its keys in a fixed order

    Why it fails

    The source describes unordered containers by their hashed lookup mechanism, not by ordering

    Do this instead

    Do not rely on ordering from an unordered container, and use the associative family when order matters

  • Common mistake

    Treating adaptors as general-purpose collections

    What it looks like

    Expecting a stack to offer every operation a vector offers

    Why it fails

    An adaptor provides a narrower interface suited to one access pattern

    Do this instead

    Use an adaptor for its access pattern and an ordinary container when you need general operations

  • Common mistake

    Mistaking array or bitset for a variable-size container, when their size is fixed as part of the type

Key Takeaways

  • Standard containers are organized into sequential, associative, and unordered families, and the family tells you whether you reach elements by position, by key, or through hashed lookup.
  • Sequential containers (vector, list, forward_list, deque) are arranged in a line, so position is the main way to reach an element.
  • Associative containers (set, map, multiset, multimap) store elements with keys, and unordered containers use hash tables and are optimized for lookup by key.
  • Container adaptors (queue, stack, priority_queue) provide a narrower interface to a container, while array and bitset are specialized, fixed-size container-like types.
  • Choose a container by asking how elements are reached, whether duplicates are allowed, and whether ordering matters.

Related concepts

More concepts starting with S