Looking up a value by key
An unordered_map answers the question "what value goes with this key?" quickly. C++ gives you several ways to ask it, and they differ in what happens when the key is missing. Knowing that difference is the main point of this lesson.
Take this map:
unordered_map<string, int> scores;
scores["Alice"] = 95;
scores["Bob"] = 87;
Square brackets
The operator [] returns the value stored under the key:
int aliceScore = scores["Alice"]; // 95
It is short and works for both reading and writing, but it has a side effect. If the key is not in the map, [] inserts it with a default-constructed value, which is 0 for an int, and returns that. After int x = scores["Zed"];, x is 0 and the map has grown from two entries to three. A lookup meant to check whether someone exists has just added them.
This also means [] cannot be used on a const map, because it might need to change the map.
The at() method
The method at() reads without inserting. When the key is missing it throws a std::out_of_range exception:
int bobScore = scores.at("Bob"); // 87
Use at() when a missing key means a bug and you want the program to say so, rather than carry on with a made-up zero. If the key might legitimately be absent, catch the exception or check first.
Checking before you read
Two tools test for a key without changing the map:
count(key)returns how many entries have that key. Keys are unique, so the answer is 0 or 1.find(key)returns an iterator that points at the entry, orscores.end()when it is missing. It lets you test and read with a single lookup.
if (scores.count("Alice") > 0) {
cout << scores["Alice"];
}
auto it = scores.find("Bob");
if (it != scores.end()) {
cout << it->second; // 87
}
For an iterator, it->first is the key and it->second is the value. After the check passes, using [] is safe, since the key is known to exist. In a loop that runs many times, find avoids searching twice.
A default for missing keys
Often the real need is "the value if present, otherwise some fallback", without growing the map. Write it with find and a conditional expression:
auto it = scores.find("Zed");
int zedScore = (it != scores.end()) ? it->second : -1; // -1, and the map is unchanged
The map has no built-in method for this, so the pattern is worth memorising. It reads the map once, it never inserts, and the fallback value is yours to choose. That last point matters when 0 is a legitimate score and cannot double as "missing".
Which one to choose
| You want | Use |
|---|---|
| A value, and a missing key is a bug | at(key) |
| To know if a key exists | count(key) or find(key) |
| Read it if present, else do something else | find(key) |
| Add or overwrite a value | [key] = value |
The exercise, step by step
The editor starts with a map named scores that holds Alice, Bob and Charlie. The task is to read Alice's score into a variable.
- Declare an
intnamedaliceScore. The score is a whole number, matching the map's value type. - Assign it
scores["Alice"]. The key is the string "Alice" in double quotes.
unordered_map<string, int> scores;
scores["Alice"] = 95;
scores["Bob"] = 87;
scores["Charlie"] = 72;
int aliceScore = scores["Alice"];
The key exists, so aliceScore is 95 and the map is unchanged, still with three entries. scores.at("Alice") would give the same answer here.
Common mistakes
- Reading a key that is not there with
[]. You silently get 0 and add an entry. A typo likescores["alice"]does not fail, because keys are case sensitive and "alice" is a different key. - Using
countas a lookup. It tells you whether the key exists, not what the value is. - Leaving an exception uncaught.
at()on a missing key ends the program if nothing catches the exception. - Treating a stored 0 as "missing". A key can really hold 0, so test with
countorfind, not by comparing the value to 0.
Key takeaways
m[key]reads, but inserts a default value if the key is missing.m.at(key)throwsstd::out_of_rangeinstead of inserting.- Use
countorfindto test for a key without changing the map.