Я только что потерял три дня своей жизни, отслеживая очень странную ошибку, когда unordered_map :: insert () уничтожает вставленную вами переменную. Это крайне неочевидное поведение наблюдается только в самых последних компиляторах: я обнаружил, что clang 3.2–3.4 и GCC 4.8 - единственные компиляторы, демонстрирующие эту «особенность».
Вот небольшой сокращенный код из моей основной базы кода, который демонстрирует проблему:
#include <memory>
#include <unordered_map>
#include <iostream>
int main(void)
{
std::unordered_map<int, std::shared_ptr<int>> map;
auto a(std::make_pair(5, std::make_shared<int>(5)));
std::cout << "a.second is " << a.second.get() << std::endl;
map.insert(a); // Note we are NOT doing insert(std::move(a))
std::cout << "a.second is now " << a.second.get() << std::endl;
return 0;
}
Я, как, вероятно, большинство программистов на C ++, ожидал, что вывод будет выглядеть примерно так:
a.second is 0x8c14048
a.second is now 0x8c14048
Но с clang 3.2-3.4 и GCC 4.8 вместо этого я получаю следующее:
a.second is 0xe03088
a.second is now 0
Это может не иметь смысла, пока вы внимательно не изучите документы для unordered_map :: insert () на http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/, где перегрузка № 2:
template <class P> pair<iterator,bool> insert ( P&& val );
Это жадная перегрузка перегрузки универсальной ссылки, которая потребляет все, что не соответствует ни одной из других перегрузок, и перемещает построение в value_type. Итак, почему наш код выше выбрал именно эту перегрузку, а не перегрузку unordered_map :: value_type, как, вероятно, большинство ожидает?
Ответ пристально смотрит вам в лицо: unordered_map :: value_type - это пара < const int, std :: shared_ptr>, и компилятор будет правильно думать, что пара < int , std :: shared_ptr> неконвертируема. Поэтому компилятор выбирает перегрузку универсальной ссылки move, которая уничтожает оригинал, несмотря на то, что программист не использует std :: move (), что является типичным соглашением для указания того, что вы в порядке с уничтожением переменной. Поэтому поведение разрушения вставки на самом деле правильное согласно стандарту C ++ 11, а старые компиляторы были неправильными .
Теперь вы, наверное, понимаете, почему мне потребовалось три дня, чтобы диагностировать эту ошибку. Это было совсем не очевидно в большой кодовой базе, где тип, вставляемый в unordered_map, был typedef, определенным далеко в терминах исходного кода, и никому не приходило в голову проверить, идентично ли typedef значению value_type.
Итак, мои вопросы к Stack Overflow:
Почему старые компиляторы не уничтожают переменные, вставленные как новые компиляторы? Я имею в виду, что даже GCC 4.7 этого не делает, и он довольно соответствует стандартам.
Широко ли известна эта проблема, ведь при обновлении компиляторов код, который раньше работал, внезапно перестанет работать?
Намерял ли комитет по стандартам C ++ такое поведение?
Как бы вы посоветовали изменить unordered_map :: insert () для улучшения поведения? Я спрашиваю об этом, потому что, если здесь есть поддержка, я намерен отправить это поведение в качестве примечания N в WG21 и попросить их реализовать лучшее поведение.
4.9.0 20131223 (experimental)
соответственно. Выход a.second is now 0x2074088
для меня (или аналогичный).
a
- нет. Он должен сделать копию. Кроме того, это поведение полностью зависит от stdlib, а не от компилятора.