The C++ Standad Template Library (STL) Containers
Containers implement Abstract Data Types such as arrays, queues, linked lists, and so on. They are classified into:
- Sequence containers, such as
vector
's (1-dimensional arrays),
's (double ended queues),deque
list
's (doubly linked lists), and so on. They provide access to sequences of elements. - Associative containers:
set
,multiset
map
andmultimap
.map
andmultimap
provide lookup based on a key.
Each container has its own advantages and disadvantages. Some aspects are:
- ease of insertion and deletion not at the back: list perform best, whereas vectors perform worst
- access by index: only vectors support it
Not-Quite Containers
In addition, the standard library provides types of objects that hold elements while not offering all of the facilities of sequence containers or associative containers:
- Container adaptors provide specialized access to underlying containers. They are
- bitmaps
- valarrays
The (Mostly) Common Containers Interface
Each container class makes some type definitions of integer types (size and index types etc), and pointer, iterator and reference types. Some common ones are:
size_type
: Some integral type roughly equivalent to size_t.difference_type
: Can represent the difference between two addresses.value_type
: The same type as the type of the values stored in a container object (i.e., the same asT
for sequential containers and container adaptors, butpair<const KType, VType>
for stl::map and stl::multimap.reference
(const_reference
): A (constant) reference to an element.pointer
(const_pointer
): A (constant) pointer to a component of a (first-class) container object (i.e., same as T*, where T is the component type of the container object).iterator
: An iterator.const_iterator
: A const iterator.reverse_iterator
: A reverse iterator.const_reverse_iterator
: A const reverse iterator.allocator_type
: The type of the allocator used for the container class (Allocators are the most infrequently used feature of the STL by the average C++ programmer. It is almost always OK to go with the default allocator for whatever container you are using.)
Additionally, container adaptors stack
, queue
, and priority_queue
define container_type
. The associative containers (set
, multiset
, map
, and multimap
) define key_type
and key_compare
. Only map
and multimap
define mapped_type
, whereas set
and multiset
define value_compare
.
Elements held in containers are meant to be accessed sequentally through iterators, which are generalizations of pointers and support as much iterator arithmetic as makes sense. Each container defines functions producing an iterators pointing to the first element and to the element past the last one:
begin()
andrbegin()
produce an iterator to the first element and a reverse iterator to the last element, and-
end()
andrend()
produce an iterator to a would-be element past the last one or a reverse iterator to another before the first one. They are meant to be used like this in for loops and such:for(iterator it = begin(); it != end(); ++it) { // body of the for-loop }
Other members commonly found in STL containers are:
- default, copy and range constructors (
);container (iterator beg, iterator past_end) void clear()
and boolempty()
for making a container empty or finding if it is empty;-
insert(...)
anderase(...)
member functions for inserting an element given an element or range of elements and an iterator to its destination, or a destination iterator and a reference. These are some common prototypes:iterator insert(iterator i, const T &val = T( )); void insert(iterator i, size_type num, const T & val); template <class InIter> void insert(iterator i, InIter start, InIter end);
Warning: Random-access insertions and deletions are slow in containers based on C arrays, such as
std::vector
andstd::deque
. size()
andmax_size()
return the current number of elements and the maximum number of elements that can be held;std::vector
andstd::string
provide acapacity()
member function that returns how many elements the container can hold before resizing and thereby invalidating references to its members or underlying C-array, which we are not advised to take anyway; we can anticipate this by callingreserve(size_type n=0)
, though;swap(const
exchanges the container's elements and those of its parameter, a facility that is used by some algorithms.container_type
&)
Requirement Compare
Compare is a set of requirements expected by some of the standard library facilities from the user-provided function object types.
The return value of the function call operation applied to an object of a type satisfying Compare
, when contextually converted to bool, yields true if the first argument of the call appears before the second in the strict weak ordering relation induced by this type, and false otherwise.
As with any BinaryPredicate, evaluation of that expression is not allowed to call non-const functions through the dereferenced iterators.
The associative containers std::set
, std::map
, std::multiset
, and std::multimap
as well as std::priority_queue
expect a Compare
type.
The following standard library functions or algorithms expect a Compare
type, too:
- sort
- sorts a range into ascending order (function template)
- sort
- sorts the elements (public member function of std::forward_list<T,Allocator>)
- sort
- sorts the elements (public member function of std::list<T,Allocator>)
- stable_sort
- sorts a range of elements while preserving order between equal elements (function template)
- partial_sort
- sorts the first N elements of a range (function template)
- partial_sort_copy
- copies and partially sorts a range of elements (function template)
- is_sorted (C++11)
- checks whether a range is sorted into ascending order (function template)
- is_sorted_until (C++11)
- finds the largest sorted subrange (function template)
- nth_element
- partially sorts the given range making sure that it is partitioned by the given element (function template)
- lower_bound
- returns an iterator to the first element not less than the given value (function template)
- upper_bound
- returns an iterator to the first element greater than a certain value (function template)
- binary_search
- determines if an element exists in a certain range (function template)
- equal_range
- returns range of elements matching a specific key (function template)
- merge
- merges two sorted ranges (function template)
- merge
- merges two sorted lists (public member function of std::forward_list<T,Allocator>)
- merge
- merges two sorted lists (public member function of std::list<T,Allocator>)
- inplace_merge
- merges two ordered ranges in-place (function template)
- includes
- returns true if one set is a subset of another (function template)
- set_difference
- computes the difference between two sets (function template)
- set_intersection
- computes the intersection of two sets (function template)
- set_symmetric_difference
- computes the symmetric difference between two sets (function template)
- set_union
- computes the union of two sets (function template)
- push_heap
- adds an element to a max heap (function template)
- pop_heap
- removes the largest element from a max heap (function template)
- make_heap
- creates a max heap out of a range of elements (function template)
- sort_heap
- turns a max heap into a range of elements sorted in ascending order (function template)
- is_heap (C++11)
- checks if the given range is a max heap (function template)
- is_heap_until (C++11)
- finds the largest subrange that is a max heap (function template)
- max
- returns the greater of the given values (function template)
- max_element
- returns the largest element in a range (function template)
- min
- returns the smaller of the given values (function template)
- min_element
- returns the smallest element in a range (function template)
- minmax (C++11)
- returns the smaller and larger of two elements (function template)
- minmax_element (C++11)
- returns the smallest and the largest elements in a range (function template)
- lexicographical_compare
- returns true if one range is lexicographically less than another (function template)
- next_permutation
- generates the next greater lexicographic permutation of a range of elements (function template)
- prev_permutation
- generates the next smaller lexicographic permutation of a range of elements (function template)
Container Adaptors
These are based on another, full container and reduce or adapt their capabilities to suite an interface.
Stack
A stack
implements a stack or FILO (First In Last Out) queue and is based on a deque
by default, although it might use a vector
just as well.
Queue
A queue
implements a (First In Last Out) queue and is based on a deque
by default, although it might use a vector
just as well.
Prority Queue
A prority_queue
keeps its elements sorted and produces the smallest element according to some comparison function object. It is otherwise called a heap and is based on a vector
by default because it requires random access and swapping elements.
You can write a customized priority queue by availing yourself of the following STL algorithms, which rely on random-access iterators, as found in vector
and queue
:
template <class RandIter> void make_heap(RandIter start, RandIter end); template <class RandIter, class Comp> void make_heap(RandIter start, RandIter end, Comp cmpfn); // template <class RandIter> void push_heap(RandIter start, RandIter end); template <class RandIter, class Comp> void push_heap(RandIter start, RandIter end, Comp cmpfn); // template <class RandIter> void pop_heap(RandIter start, RandIter end); template <class RandIter, class Comp> void pop_heap(RandIter start, RandIter end, Comp cmpfn);
Or you can turn a sequence into a heap all at once by calling:
template <class RandIter> void sort_heap(RandIter start, RandIter end); template <class RandIter, class Comp> void sort_heap(RandIter start, RandIter end, Comp cmpfn);
Sequence Containers
Doubly-Linked Lists
A list is a sequence optimized for insertion and deletion of elements. When you insert into a list or delete an element from a list , the locations of other elements of the list are not affected. In particular, iterators referring to other elements are not affected.
The STL provides two linked-list types:
- list: a doubly-linked list, and
- forward_list: a singly-linked list introduced in recent revisions (C++11)
These are implemented in container list<>
.
st.push_front(x) | Add x to lst (using copy or move) before the first element |
lst.pop_front() | Remove the first element from lst |
lst.emplace_front(args) | Add T{args} to lst before the first element |
lst.remove(v) | Remove all elements of lst with value v |
lst.remove_if(f) | Remove all elements of lst for which f(x)==true |
lst.unique() | Remove adjacent duplicate elements of lst |
lst.unique(f) | Remove adjacent duplicate elements of lst using f for equality |
lst.merge(lst2) | Merge the ordered lists lst and lst2 using < as the order; lst2 is merged into lst and emptied in the process |
lst.merge(lst2,f) | Merge the ordered lists lst and lst2 using f as the order; lst2 is merged into lst and emptied in the process |
lst.sort() | Sort lst using < as the order |
lst.sort(f) | Sort lst using f as the order |
lst.reverse() | Reverse the order of the elements of lst ; noexcept |
Vectors
std::vectors
are automatic arrays which keep track of their size, that is how many places have been occupied (pressumably in an integer variable), as well as their capacity, which is the total number of objects of the given type that they can accomodate. Further, when they have filled up, they reallocate their sequence of objects in a larger chunk of memory, the start of this run of memory is updated (in a pointer variable), and the new, larger capacity is updated, too. This means that indeces into the array-like structure remain valid, but pointers are invalidated, so they must never be taken in the first place.
Unlike with linked lists, insertions (or deletions) are either meaningless or demand that all objects after the one inserted be shifted one place, which is done in linear time, whereas lists perform insertions and deletions in constant time. Furthermore, insertions and deletions would invalidate all subsequent indeces...
Deques
A deque
(pronounced "deck") is very similar to a vector. It manages its elements with a dynamic array, provides random access, and has almost the same interface as a vector. The difference is that with a deque
the dynamic array is open at both ends. Thus, a deque
is fast for insertions and deletions at both the end and the beginning.
Deques have the following differences compared with the abilities of vectors:
- Inserting and removing elements is fast both at the beginning and at the end (for vectors it is only fast at the end). These operations are done in amortized constant time.
- The internal structure has one more indirection to access the elements, so element access and iterator movement of
deque
's are usually a bit slower. - Iterators must be smart pointers of a special type rather than ordinary pointers because they must jump between different blocks.
- In systems that have size limitations for blocks of memory (for example, some PC systems), a
deque
might contain more elements because it uses more than one block of memory. Thus, max_size() might be larger fordeque
's. - Deques provide no support to control the capacity and the moment of reallocation. In particular, any insertion or deletion of elements other than at the beginning or end invalidates all pointers, references, and iterators that refer to elements of the
deque
. - However, reallocation may perform better than for vectors, because according to their typical internal structure,
deque
's don't have to copy all elements on reallocation. - Blocks of memory might get freed when they are no longer used, so the memory size of a
deque
might shrink (however, whether and how this happens is implementation specific).
Bitmap's*
Associative Containers
An associative container is a variable-sized container that supports efficient retrieval of elements (values) based on keys. It supports insertion and removal of elements, but differs from a sequence container in that it does not provide a mechanism for inserting an element at a specific position but the container itself keeps its elements in order internally in a suitable data structure.
Immutability of Associative Containers
Since the elements of an associative container are stored according to their keys (in a binary tree, hash table...), it is essential that the key associated with each element is immutable. In simple associative containers this means that the elements themselves are immutable, while in other types of associative containers, such as pair associative containers, the elements themselves are mutable but the part of an element that is its key cannot be modified or assigned a value to. This has an important consequence: associative containers cannot have mutable iterators in the sense that if it is an iterator for an associative container and t is an object of it's value type, then *it = t is not a valid expression.
Consequently, every element of a simple associative container is immutable: objects may be inserted and erased, but not modified.
Types of Associative Containers
Associative containers are either maps or sets. A map associates or maps
a key to a value, whereas in a set (set
and multiset
) their elements are their own keys.
A map may be either a stl::map
or a stl::multi_map
, and a set may be either a stl::set
or a stl::multi_set
. Here the prefix multi
means that elements with the same key are allowed. Furthermore, sameness is to be understood not literally but according to a function or a function object that takes to objects and returns a boolean value.
According to their implementation and performance, associative containers are further divided into:
- ordered containers: and are
set
,multiset
,map
, andmultimap
. They are implemented as binary trees, often red-black trees. They use a comparison function or a comparison function object to compare keys. - unordered containers: and are
unordered_set
,unordered_multiset
,unordered_map
, andunordered_multimap
. According to Bjarne Stroutstrup, they are implemented as hash tables with linked overflow.
Comparison Function Objects for STL's Associative Containers
Introduction
To use the Standard Template Library (STL) associative containers, the type of keys stored must be comparable.
For sorted containers (e.g std::set
), the comparison is by equivalence, which roughly means overloading the operator<
, and for unsorted containers (e.g std::unordered_set
) the comparison is by equality, which in turn (again, roughly) means overloading the operator==
.
Custom Comparison for STL's Associative Containers
For some instances of a container with a predefined comparison function for the stored type, you may want to supply an alternative comparison function that makes more sense for the container instance's purpose.
For example, given a type Person
that are normally sorted by Person::id, for some std::set<Person>
called names you may want to compare the elements by Person::name instead of Person::id. How do you override
the default comparison function for Person to achieve the desired behavior for this particular instance of std::set<Person>
?
The Complete Declaration for Associative Containers
To exemplify the concepts, I'll consider std::set
as a representative of the STL's associative containers, the differences between it and the others aren't significant for our purpose: how to create an associative container with a different comparison function from the one defined by the type of element held inside the container?
The complete declaration for std::set is:
template < class T, class Compare = less<T>, class Alloc = allocator<T> > class set;
Where:
T
is the type of elements that will be held- Alloc is the memory allocator, with the default being
allocator<T>
And the most important aspect for this discussion:
Compare is the type of the comparison used, with the default being std::less<T<
But, what's std::less<T<
?
std::less<T<
is a function object that expresses the less-than
semantic for objects of type T
. Its behavior is to call the operator<()
overloaded by the type T
to check the relative order of two objects A and B of type T
.
Given the declaration for std::set
, we can see that the way for providing our desired behavior is to supply a different type of comparison function object for the template parameter: Compare
. Alright? Let's do it then!
Our type Person
looks like this:
#include <algorithm> #include <functional> #include <iostream> #include <set> #include <string> class Person { public: explicit Person(int id, const std::string &name) : m_id{id}, m_name{name} {} const int id() const {return m_id;} const std::string& name() const {return m_name;} private: const int m_id; const std::string m_name; }; bool operator<(const Person &lhs, const Person &rhs) { // compare by id return lhs.id() < rhs.id(); } std::ostream& operator<<(std::ostream &os, const Person &rhs) { // for formatted output os << "Id: " << rhs.id() << ", Name: " << rhs.name(); return os; }
And the use of Person
inside std::se
t and comparing by Person::name instead of Person::id can be achieved by:
struct PersonCompareByName { bool operator()(const Person &lhs, const Person &rhs) const { return lhs.name() < rhs.name(); } }; int main() { auto people = {Person{2, "C"}, Person{3, "A"}, Person{1, "B"}}; // By Id std::set<Person> peopleById = people; std::cout << "By Id: " << "\n\n"; std::copy(cbegin(peopleById), cend(peopleById), std::ostream_iterator<Person>{std::cout, "\n"}); std::cout << '\n'; // By Name (1) std::set<Person, PersonCompareByName> peopleByName = people; std::cout << "By Name (1): " << "\n\n"; std::copy(cbegin(peopleByName), cend(peopleByName), std::ostream_iterator<Person>{std::cout, "\n"}); std::cout << '\n'; // By Name (2) auto personComparator = [](const auto &lhs, const auto &rhs) {return lhs.name() < rhs.name();}; std::set<Person, decltype(personComparator)> peopleByName2{people, personComparator}; std::cout << "By Name (2): " << "\n\n"; std::copy(cbegin(peopleByName2), cend(peopleByName2), std::ostream_iterator<Person>{std::cout, "\n"}); return 0; }
First, we'd created the peopleById that uses the internal (default) comparison function for the type Person: By Person::id(). Thus, every element inside the container will be sorted based on its id.
Then, at (1) we created peopleName that relies on the function object PersonCompareByName
as the argument for the template parameter Compare
, establishing the comparison by Person::name().
At (2) we achieved the same behavior of (1) but employing an alternative syntax that uses the lambda personComparator and supplied the template parameter with decltype.
Function Objects for Ordered Associative Containers
Ordered associative containers use compare function objects to keep themselves sorted. The default upon construction is less<Key>
, which is defined in bool operator()(const Key& l, const Key& r)
and meet the following three requirements for a strict weak ordering:
- It has to be antisymmetric
- That is, if less<Key>(val1,val2) is true, then less<Key>(val2,val1) must be false.
- It has to be transitive
- It has to be irreflexive
- That is, a key is never less than itself, or more formally less<Key>(val,val) is always false.
On this definition we define an equality test given two elements: they are equal if neither is less than the other.
set
and multiset
Set and multiset containers sort their elements automatically according to a certain sorting criterion. The difference between the two is that a multiset
allows duplicates, whereas a set
does not.
They are good for finding out if an element is in a set, or for retrieving an element that is equal
to another, which actually is useful if function object less<T>
is specialized to suit our goals.
Search, removal, and insertion operations have logarithmic complexity. Sets are usually implemented as red-black trees.
Using a std::set
for Recording Visited
Items
We will be using the following:
- insertions, and possibly deletions
-
const_iterator std::set::find( const Key& key ) const
const_iterator std::set::end() const
for comparing the returned value
This would enable us to write a bool has(s, k) const
, and a similar bool has_or_add(s, k)
function.
In many situations the key type would be just a pointer...
map
and multimap
*
Difference Between std::map::operator[](KEY)
and std::map::at(KEY)
f you access a key using the indexing operator [] that is not currently a part of a map, then it automatically adds a key for you. This is a huge caveat. For this reason, I prefer using the indexing operator [] for setting, and members find()
/ at()
for lookup.
Another advantage of using at()
over []
is the fact that it can operate on a const std::map, whereas []
won't.
at(key)
throws an exception if the key doesn't exist.
Random Access Returning Reference
Sequence containers based on an array offer access through indeces. They will declare:
reference operator[](size_type idx); const_reference operator[](size_type idx) const;
These are fast but do not perform bound checking. Trying to access beyond the end of the underlying array may, well, crash the program or yield the wrong result. If unsure, use:
reference at(size_type idx); const_reference at(size_type idx) const;
Member function at(size_type idx)
returns a reference
or const_reference
all right, but will throw an out_of_range
exception. Use at(size_type idx)
inside a try {...} block.
If you write a container in the STL tradition that returns references to elements of an array, write the body of your at(size_type idx)
like this:
if(idx >= size()) throw out_of_range("Container::at() used with idx past the end"); else return the_array[idx];
Find and Replace Operations
Functions find(...)
returns a size_type variable, actually an integer representing an index to the element found. A string
's find()
operation may return npos, for no position
, which should be defined as
Some Prototypes for Sequence and Associative Containers
This is the prototype of a would-be sequence_container
class to derive containers from. There are no private members since this class is used as an interface.
template <class T, class Allocator=allocator<T> > class sequence_container { public: // some type definitions: typedef unsigned int size_type; typedef T value_type; typedef T * iterator; typedef const T * const_iterator; typedef T & reference; typedef const T & const_reference; // this is the type of function that compares two values: typedef bool (*) (const T&, const T&) value_compare; // some more obscure type definitions: typedef T * reverse_iterator; typedef const T * const_reverse_iterator; typedef Allocator allocator_type; // some iterator-related member functions: iterator begin(); const_iterator begin() const; iterator end(); const_iterator end() const; reverse_iterator rbegin(); const_reverse_iterator rbegin() const; reverse_iterator rend(); const_reverse_iterator rend() const; // some other member functions: size_type size() const; reference front(); const_reference front() const; void pop_back(); void push_back(const T& val); void clear(); bool empty() const; allocator_type get_allocator() const; // constructors: sequence_container(const Allocator &ca = Allocator())); sequence_container(size_type num, const T& val = T(), const Allocator &ca = Allocator())); sequence_container(const sequence_container<T,Allocator>& obj); template <class InIter> sequence_container(InIter start, InIter end, const Allocator& ca = Allocator()); };
Emplacing
The emplace()
operation is used when it is notationally awkward or potentially inefficient to first create an object and then copy (or move) it into a container. Read more.