15.6. The map class¶
A map refers to any data structure that maps keys to
values. The standard-library map stores each key together with its mapped
value in a std::pair.
The containers discussed so far have focused on storing one thing at a time.
That is, each stores values of a single type.
Maps add a new wrinkle.
A map stores pairs of things.
Traditionally, the pair stored is referred to as a key-value pair.[1]
Nearly every programming language provides some kind of map implementation.
Some languages use the terms associative array or dictionary,
but structurally, they are very similar.
Values are retrieved from a map using the key.
Each key must be unique.
In other words, keys are members of a set.
Like a std::set, inserting a second node with the same key
has no effect. The mapped value can be updated through operator[] or
insert_or_assign.
Unlike a std::set,
a std::map provides the map::operator[].
The examples on this page use the following shared setup:
#include <iostream>
#include <functional>
#include <map>
#include <set>
#include <string>
std::map<std::string, int> sample_inventory() {
return {
{"apple", 12},
{"kiwi", 4},
{"lemon", 1},
{"pear", 4},
{"peach", 4},
{"grape", 100},
{"cocoa", 3}
};
}
The following complete example demonstrates iteration, updating an existing
mapped value, inserting through operator[], and checked access with
at:
1int main() {
2 std::map<std::string, int> name_counts {
3 {"Alice", 27},
4 {"Bob", 3},
5 {"Clara", 1}
6 };
7
8 for (const auto& kvp : name_counts) {
9 std::cout << kvp.first << ": " << kvp.second << '\n';
10 }
11
12 name_counts["Bob"] = 42; // update an existing value
13 name_counts["Darla"] = 9; // insert a missing key
14
15 std::cout << "Bob is " << name_counts.at("Bob") << '\n';
16 std::cout << "Darla is " << name_counts["Darla"] << '\n';
17}
operator[] default-initializes a mapped value when the key is absent.
Use at when a missing key should be reported instead; at throws
std::out_of_range for an absent key. Neither operation changes the key
ordering.
The map::insert function follows the same unique-key
contract as set::insert: an equivalent key is not inserted and an existing
mapped value is not overwritten. Use insert_or_assign when an update is
intended.
C++17 Feature
C++17 added insert_or_assign and try_emplace for code that wants
to state its insertion or update intent explicitly. insert_or_assign
updates an existing mapped value, while try_emplace does nothing when
the key already exists and constructs the mapped value only when needed.
1int main() {
2 auto inventory = sample_inventory();
3 inventory.insert_or_assign("kiwi", 10);
4 inventory.try_emplace("mango", 6);
5
6 std::cout << inventory.at("kiwi") << ' '
7 << inventory.at("mango") << '\n';
8}
C++20 Feature
C++20 added map::contains for membership checks when the mapped value is not needed.
1int main() {
2 const auto inventory = sample_inventory();
3 std::cout << std::boolalpha
4 << inventory.contains("kiwi") << ' '
5 << inventory.contains("mango") << '\n';
6}
C++20 Feature
C++20 also provides std::erase_if for removing map entries based on their key-value pairs.
1int main() {
2 auto inventory = sample_inventory();
3 std::erase_if(inventory, [](const auto& entry) {
4 return entry.second < 5;
5 });
6
7 for (const auto& entry : inventory) {
8 std::cout << entry.first << ": " << entry.second << '\n';
9 }
10}
15.6.1. Selected map functions¶
- Access and assignment
- Capacity
- Modifiers
- Lookup
count, find, equal_range, upper_bound, lower_bound, and contains
For an ordered map, lookup, insertion, and erasure take \(O(\log N)\)
time and the container uses \(O(N)\) storage. A sequential container may
be faster for small data sets or workloads that benefit from contiguous
storage, so the choice should be based on the operations the program needs.
Note
There is no push_back() for a map.
The map decides where elements go, not you.
All access requires either knowing the key or having an iterator.
15.6.2. Map structure¶
Internally, a map is an ordered tree-like structure. Implementations often
use a self-balancing tree such as a Red-black tree;
the standard does not require a particular implementation.
Each node in the tree is a std::pair.
Map entries are ordered by their keys, using a comparison object
that defines a strict weak ordering. The default comparison
uses operator<, but the map constructor can receive a custom comparator,
just as with a set.
The map's value_type is a pair whose first member is const. A key
cannot be changed through an iterator because that could violate the ordering
invariant, but the mapped value can be changed.
All map entries are visited in key order according to the map's comparison
object. By default, that is ascending order according to operator<. The
following example extracts keys into a set and then uses upper_bound
to find the first key greater than "kiwi".
1int main() {
2 const auto inventory = sample_inventory();
3 std::set<std::string> inventory_keys;
4
5 for (const auto& entry : inventory) {
6 inventory_keys.insert(entry.first);
7 }
8
9 std::cout << "All fruit keys:\n";
10 for (const auto& key : inventory_keys) {
11 std::cout << key << ' ';
12 }
13
14 std::cout << "\nKeys greater than kiwi:\n";
15 for (auto it = inventory.upper_bound("kiwi");
16 it != inventory.end();
17 ++it) {
18 std::cout << it->first << ' ';
19 }
20 std::cout << '\n';
21}
Maps can use a custom comparator just like sets. This complete example stores the same entries in descending key order:
1int main() {
2 const auto inventory = sample_inventory();
3 const std::map<std::string, int, std::greater<std::string>> reverse_inventory {
4 inventory.begin(), inventory.end()
5 };
6
7 for (const auto& entry : reverse_inventory) {
8 std::cout << entry.first << ": " << entry.second << '\n';
9 }
10}
15.6.3. Variations on std::map¶
The standard library provides related ordered and unordered containers:
- multimap
A
mapin which duplicate keys are allowed.- unordered_map
A map of unique keys organized by a hash function, not by sorted order. Each key still maps to one mapped value. Added in C++11.
A key type needs a hash function and an equality predicate. The standard library provides these for many built-in and library types. For a user type, provide a
std::hash<Key>specialization or a custom hash object, and provide equality throughoperator==or a custom equality object.- unordered_multimap
An
unordered_mapin which duplicate keys are allowed.
More to Explore