Concepts / Specialized Containers in the C++ Standard Library

Specialized Containers in the C++ Standard Library

Specialized containers are array, bitset, pair, and tuple.

  • Programming

Why Programs Must Give Things Back

Most programs do real work by obtaining things from their environment and then using them. A program might reserve memory, open a file, take a lock so that only one part of the program touches shared data at a time, open a network socket, or hold a handle to a thread. The C++ Standard Library and the source material that introduces this topic call each of these things a resource.

Definition

A resource is something that a program must acquire and later release. The release may be explicit, meaning the programmer asks for it, or implicit, meaning it happens automatically. Acquiring and releasing are the two halves of the same obligation.

The list of examples spans very different things: memory, locks, sockets, thread handles, and file handles. These share the same shape. A program obtains them, uses them, and is responsible for giving them back. The important idea is the shared lifecycle, not the particular kind of resource. Once you see that pattern, you can recognize resource handling in many places, even ones this article does not name.

Tracing a Resource Through Its Lifecycle

Any resource moves through a cycle. It begins in an unacquired state, is acquired, is used for some work, and eventually reaches a point where it must be released. Follow one resource through a simple scenario and watch its state at each step. Suppose a program opens a file handle to read a configuration value. Before the open call, the handle is unacquired: the program does not have it. After the open call succeeds, the handle is acquired and held by the program. While the program reads from it, the handle is in use. When the program has finished reading, the handle must be released so that it is given back.

UnacquiredAcquiredIn useReleasedacquireuse for workrelease
What state is a resource in after it is acquired, during use, and after it is released, and how does that state change at each step?

Notice where the release step sits in the cycle. It is the last transition, and it is the one most easily forgotten. Nothing visibly breaks at the moment it is skipped. The program keeps running, the file handle is simply still held when the program no longer needs it. That is why the lifecycle is worth keeping in mind from the very start of any resource-handling code.

What Happens When a Release Is Missed

The source calls a failure to release a resource in a timely manner a leak. The consequence does not appear immediately. For a long-running program, a leak can cause serious performance degradation and possibly a miserable crash. The problem accumulates: each unreleased resource is a little more that the program is holding onto without need.

Resource still heldPerformance degradesPossible crashResource releasedNothing held withoutneedaccumulatesover a long runtimely release
How does the program's resource usage differ when a resource is released promptly versus left held after it is no longer needed?

The two columns of the diagram describe the same scenario with one difference: whether the final release step happened. In the left column the handle stays held after the work is done, and the cost grows over time. In the right column the handle is given back, and the program holds nothing it does not need.

Four Component Groups on the Map

This part of the library is introduced as a map of small, widely useful components in four groups. Resource management covers the smart pointers that help a program manage resources. Specialized containers are array, bitset, pair, and tuple. Function adaptors include iterator_traits, Type Predicates, and mem_fn(). Smart pointers are unique_ptr and shared_ptr. The source names these components without fully explaining them, so treat this section as a map and continue with the detailed material for each group.

The central observation of this group is that a function or a type does not need to be complicated, or tightly bound to a large cluster of other functions and types, to be valuable. Components like these mostly serve as building blocks. Larger library facilities are assembled from them, so understanding the small pieces helps when you later meet the larger ones.

The Four Specialized Containers

The C++ Standard Library includes a group of containers that have specific uses. This article calls them specialized containers. They are the components the library offers for storing collections of values when a more targeted tool is needed than a general-purpose container. The source names four of them: array, bitset, pair, and tuple. Each one is a container in its own right, and each has its own purpose.

ContainerWhat it holdsTypical use
arraya collection of values of one typea fixed set of values accessed by index
bitseta collection of individual bitsstoring and working with on/off flags
pairexactly two valuesreturning or grouping two related values together
tuplea group of values that can have different typesgrouping several values that may differ in type

The four specialized containers at a glance. The uses are described for orientation; the code examples below show each one in practice.

arraya collection of valuesbitseta collection of bitspairexactly two valuestuplea group of mixed values
How do array, bitset, pair, and tuple differ in the kind and number of values they store and how they are used?

Read the diagram as a family relationship. The four containers are siblings, all grouped under specialized containers. The source does not rank them, and none is described as a version of another, so treat them as four separate tools that share a category.

Predicting Which Container Holds Each Value

What do you think happens?

Before reading the answer, predict what the bitset line prints when a bitset of size 8 is constructed from the number 5 and then printed.

Reveal answer
Answer:

00000101

A bitset of size 8 holds eight bits. The value 5 is 101 in binary, so the eight bits are 00000101 when written from most significant to least significant.

Worked example

Tracing which container holds each printed value

Given three printed lines from a program that creates one value of each specialized container, decide which container each line came from. The lines are: 75, then 00000101, then Ada.

  1. 1
    Look at the first line

    The value 75 is a single integer, and it was obtained by reading one position in a collection of values of one type. That points to array, because scores[1] reads an element by index.

  2. 2
    Look at the second line

    The value 00000101 is a string of eight zeros and ones. Each character represents one bit, so this comes from a bitset.

  3. 3
    Look at the third line

    The value Ada is a string obtained from the first position of a grouping of two values. That points to pair, because person.first reads the first of exactly two values.

Result

75 comes from array, 00000101 comes from bitset, and Ada comes from pair. The tuple in the same program was not printed by these lines.

EASY

Using the tuple reading from the earlier program, predict the output of std::cout << std::get<1>(reading) << std::endl; where reading holds the values 7, 2.5, and 'k' in that order. Which container gives you this value, and what position number reaches it?

Hints
  • Count positions from 0: position 0 holds 7, position 1 holds 2.5, and position 2 holds k.
  • The container is a tuple because it groups values that may have different types.

Common Mistakes Beginners Make

  • Common mistake

    Treating a missed release as a problem that shows up right away

    What it looks like

    A program opens a file handle, finishes reading, and never releases it, yet still prints correct output.

    Why it fails

    The source notes that nothing visibly breaks when the release is missed. The leak accumulates and the consequence appears later.

    Do this instead

    Plan the release step at the same time as the acquire step, and keep the full lifecycle in mind from the start.

  • Common mistake

    Assuming all four specialized containers are versions of one another

    What it looks like

    Using pair when a bitset of flags is needed because both seem like small holders.

    Why it fails

    The source states that none of the four is described as a version of another. Each has its own purpose.

    Do this instead

    Choose the container by what kind of values you hold: a collection of values, a set of bits, exactly two values, or a mixed group.

  • Common mistake

    Reaching a tuple value with a name instead of a position

    What it looks like

    Writing reading.first for a tuple, which holds several values.

    Why it fails

    first belongs to pair. A tuple value is reached by its position number, using std::get.

    Do this instead

    Use std::get<2>(reading) to reach the element at position 2, counting from 0.

  • Common mistake

    Assuming the source fully explains unique_ptr and shared_ptr in this section

Where to Go Next

  1. 1
  2. 2
  3. 3

Key Takeaways

  • A resource is something a program must acquire and later release, either explicitly or implicitly, and examples include memory, locks, sockets, thread handles, and file handles.
  • A failure to release a resource in a timely manner is called a leak, and it can cause serious performance degradation or a crash, especially in long-running programs.
  • The section introduces four component groups: resource management, specialized containers, function adaptors, and smart pointers.
  • The four specialized containers are array, bitset, pair, and tuple, and each is a separate tool with its own purpose.
  • array and bitset hold collections of values and bits, while pair and tuple group values that are reached by position.

Related concepts

More concepts starting with S