Specialized Containers in C++
A single general-purpose container that satisfies both members of either pair is not possible.
When a Container Does Not Quite Fit
Most of the standard library's containers follow one familiar pattern. They hold elements and provide a common way to work with them. That shared pattern is what lets a programmer learn one container and carry much of that knowledge to the next. Some types in the library also hold elements, yet they do not fit the pattern cleanly. This article is about those types and about the wider family of specialized containers that the standard library provides.
What do you think happens?
Before reading on: is a built-in array a container in the sense the standard library uses the word?
Reveal answer
Yes, in the sense that it holds elements. But it is an almost container, because it has restrictions or added facilities that make it awkward to treat the same way as the typical STL container.
An almost container holds elements and is therefore a container, but its shape differs from the usual STL pattern, usually through a restriction or an added facility.
What Makes a Type an Almost Container
An almost container is a type that holds elements and is therefore a container, but that has restrictions or added facilities which make it awkward within the STL. The label is informal, and the source itself points out that it is not entirely accurate. What the label captures is that each such type is shaped differently from the typical STL container.
The source names three examples of almost containers: built-in arrays, std::array, and std::string. Each one stores elements, so each counts as a container in the broad sense. Each one also departs from the common STL pattern, and the departure is the reason it is grouped separately.
Why group these types separately at all? The source gives a practical reason. If the general rules of the STL had to absorb every exception, the description of the framework would become harder to follow. Describing the almost containers on their own keeps the description of the STL itself simpler.
Why the Library Offers So Many Containers
A reasonable question follows from the list of almost containers: why does the standard library provide so many containers at all? The source answers that the library's containers serve common needs. Those needs are common, but they are also different from one another, and they often overlap. If the library did not supply these containers, many programmers would have to design and implement their own versions.
The source explains why one container cannot answer every need. Some needs contradict each other. It gives two pairs of conflicting requirements. The first pair is the ability to grow set against being guaranteed to be allocated in a fixed location. The second is elements not moving when new elements are added set against elements being contiguously allocated. A single general-purpose container that satisfies both members of either pair is not possible.
- Conflict one: the ability to grow, versus being guaranteed to be allocated in a fixed location
- Conflict two: elements not moving when new elements are added, versus elements being contiguously allocated
- Overhead: a very general container would carry overhead that is unacceptable for many individual uses
There is a second reason as well. A very general container would carry overhead that is unacceptable for many individual uses. Specialized containers exist so that each one can provide the services a particular group of programmers needs without paying for services it does not need. This is why the library is organized as a set of specialized containers rather than a single all-purpose one.
Sorting Containers by Three Characteristics
Rather than memorizing the specialized containers as a flat list, it helps to sort them by three characteristics: the kind of elements they hold, how those elements are allocated in memory, and how elements are accessed. Each characteristic is a question you can ask of any container. The three answers together form a profile that makes a container easier to remember and easier to choose.
Tracing Four Containers Through the Three Questions
Applying the three questions one container at a time shows how the characteristics combine. Work through each container below, answering the element type question, then the allocation question, then the access question.
Classifying pair, vector, map, and bitset
Place each of four containers on the three classification axes: element type, allocation style, and access style.
- 1Pair
Element type: heterogeneous, since pair is named as heterogeneous. Allocation and access are not given a separate placement in the source, so they are not claimed here.
- 2Vector
Element type: homogeneous, since every container other than pair and tuple is homogeneous. Allocation: contiguous, since the source names vector as contiguously allocated. Access: direct, since it is not one of the bit-holding containers.
- 3Map
Element type: homogeneous. Allocation: linked structure, since the source names map as one. Access: direct.
- 4Bitset
Element type: homogeneous. Allocation is not stated for bitset in the source, so no allocation placement is claimed. Access: through proxy objects, since the source names bitset as a bit-holding container that uses them.
Vector sits on the contiguous side of the allocation axis and map on the linked side. Pair differs from both on the element type axis, and bitset differs from both on the access axis. No single container occupies the same position on all three axes as another.
Choosing a Container for a Stated Need
The characteristics are most useful when applied to a concrete need. The scenario below is an original illustration built on the three characteristics. It is not taken from the source. Imagine you must store a single record that holds a name and a count, where the two values are of different types. The first question decides the search: because the elements are of mixed types, a homogeneous container is not the right starting point.
Following the sketch, the mixed-type requirement leads to pair or tuple, both of which the source calls heterogeneous. Pair is the simpler choice when only two values are stored. The flowchart is a simplified reading of the source's three characteristics rather than a complete selection rule, so a real decision should also weigh the conflicting requirements discussed earlier.
Mistakes When Classifying Containers
Common mistake Treating every container as homogeneous
What it looks likeAssuming a pair must hold two values of the same type.
Why it failsPair and tuple are heterogeneous, so their elements may be of different types.
Do this insteadCheck the element type axis first, and remember that pair and tuple are the named exceptions.
Common mistake Assuming an almost container cannot hold elements
What it looks likeConcluding that a built-in array is not a container because it is labeled an almost container.
Why it failsAn almost container holds elements and is therefore a container. The label refers to restrictions or added facilities, not to a lack of elements.
Do this insteadRead almost container as a container with an awkward shape, not as a non-container.
Common mistake Grouping linked and contiguous containers together
What it looks likeTreating map and vector as having the same allocation style.
Why it failsThe source places vector among contiguously allocated containers and map among linked structures.
Do this insteadSort by allocation style explicitly, using the contiguous and linked groups from the source.
Common mistake Confusing access through proxy objects with direct element access
Practice: Classify and Choose
Using the three questions, classify forward_list. Then say which characteristic would most strongly rule it out as a choice if you needed contiguous allocation, and explain your reasoning in one or two sentences.
Hints
- The source names
forward_listamong linked structures on the allocation axis. - Ask whether the element type and access style are stated for
forward_listbefore placing it on those axes.
A scenario for you to classify: you need a container that holds a fixed set of same-typed values in one contiguous block and that you access directly by position. Name a container from the source that fits, and state which of the three axes made it the match.
Hints
- Start with the element type question, since the elements are described as same-typed.
- Then check the allocation question for contiguous storage.
Key Takeaways
- An almost container holds elements and is therefore a container, but it has restrictions or added facilities that make it awkward within the STL. The source names built-in arrays, std::array, and std::string as examples.
- The standard library provides many containers because common needs differ and often overlap, and some needs contradict each other, so no single container serves every need.
- Specialized containers can be classified by element type (pair and tuple are heterogeneous, the rest homogeneous), by allocation style (contiguous or linked), and by access style (direct or through proxy objects for bitset and vector of bool).
- Choosing a container means applying the three classification questions to a stated need and accepting the tradeoffs that come with the chosen container.