Удивительное дело, но в этой заметке не будет ничего, связанного с неопределенным поведением. По крайней мере напрямую.
В стандартной библиотеке C++ много неоднозначных решений. Одно из таких: объединить операцию вставки в ассоциативный контейнер и получения элемента.
operator []
для ассоциативных контейнеров, пытается вызвать конструктор по умолчанию для элемента, если не находит переданный ключ.
С одной стороны это удобно:
std::map<Word, int> counts;
for (Word c : text) {
++counts[word]; // ровно один поиск по ключу
}
В иных языках придется постараться, чтобы записать то же самое и не допустить повторного поиска.
map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1); // поиск трижды!
map.put(key, map.getOrDefault(key, 0) + 1); // поиск дважды!
Оно, конечно, может быть, отоптимизируется JIT-компилятором... Но мы в 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[]
, конечно же, проще, «понятнее» и короче. Но и ловушка для невнимательных.
А если еще и с мерзопакостными особенностями других объектов скрестить...
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
, бросающий исключения. Либо всеми любимый:
if (auto it = m.find(key); it != m.end()) {
// делай что хочешь с *it, it->second
}
Все просто.