Files
2024-05-26 23:24:57 +01:00

90 lines
4.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `operator []` ассоциативных контейнеров
Удивительное дело, но в этой заметке не будет ничего, связанного с неопределенным поведением. По крайней мере напрямую.
В стандартной библиотеке C++ много неоднозначных решений. Одно из таких: объединить операцию вставки в ассоциативный контейнер и получения элемента.
`operator []` для ассоциативных контейнеров, пытается вызвать конструктор по умолчанию для элемента, если не находит переданный ключ.
С одной стороны это удобно:
```C++
std::map<Word, int> counts;
for (Word c : text) {
++counts[word]; // ровно один поиск по ключу
}
```
В иных языках придется постараться, чтобы записать то же самое и не допустить повторного поиска.
```Java
map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1); // поиск трижды!
map.put(key, map.getOrDefault(key, 0) + 1); // поиск дважды!
```
Оно, конечно, может быть, отоптимизируется JIT-компилятором... Но мы в C++ любим гарантии.
С другой стороны, этот вызов конструктора, если элемент не найден, может выйти боком:
```C++
struct S {
int x;
explicit S (int x) : x {x} {}
};
std::map<int, S> m { { 1, S{2} }}; // Ok
m[0] = S(5); // огромная трудночитаемая ошибка компиляции
auto s = m[1]; // опять огромная трудночитаемая ошибка компиляции
//-------------------
struct Huge {
Huge() {
data.reserve(4096);
}
std::vector<int> data;
};
std::map<int, Huge> m;
Huge h;
... /* заполняем h */
m[0] = std::move(h); // бесполезный вызов default-конструктора, лишняя аллокация,
// а потом перемещение
```
Чтобы выпутаться из этой неприятности, у ассоциативных контейнеров к C++17 (20) наплодили целую гору методов `insert_or_assign`, `try_emplace` и `insert` с непонятным для непосвященных возвращаемым значением `pair<iterator, bool>`.
Всем этим добром, конечно же, пользоваться тяжело и неудобно. Про них пишут длинные статьи в блоги о том, как эффективно пользоваться поиском по контейнерам...
С `operator[]`, конечно же, проще, «понятнее» и короче. Но и ловушка для невнимательных.
А если еще и с мерзопакостными особенностями других объектов скрестить...
```C++
std::map<std::string, std::string> options {
{"max_value" : "1000"};
}
....
const auto ParseInt = [](std::string s) {
std::istringstream iss(s);
int val;
iss >> val;
return val;
};
const int value = ParseInt(options["min_value"]); //!перепутали !нет такого поля
// value == 0. Все "ok". Счастливой отладки
// operator[] вернул пустую строку
// >> сфейлился и записал ноль в результат
```
Избежать неприятностей с `operator[]` для ассоциативных контейнеров можно, навесив `const`.
И тогда вам этот оператор доступен не будет. И придется использовать либо `.at`, бросающий исключения. Либо всеми любимый:
```C++
if (auto it = m.find(key); it != m.end()) {
// делай что хочешь с *it, it->second
}
```
Все просто.