Решено: Это ошибка в libstdС++ < v4.8.2, который GCC v4.8 и clang >= v3.2 будет использоваться, если он присутствует в системе. См. http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57619 для отчета. Спасибо Кейси и Брайану за то, что он дал правильный ответ. Найл
Оригинальный вопрос:
Я только что потерял три дня своей жизни, отслеживая очень странную ошибку, где 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;
}
Я, как и большинство программистов на С++, ожидал, что вывод будет выглядеть примерно так:
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/, где перегрузка no 2:
template <class P> pair<iterator,bool> insert ( P&& val );
Что такое жадная универсальная перегрузка ссылок, потребляющая все, что не соответствует какой-либо другой перегрузке, и переместите ее в value_type. Итак, почему наш код выше выбирает эту перегрузку, а не перегрузку unordered_map:: value_type, как ожидается, большинство из них ожидали бы?
Ответ смотрит вам в лицо: unordered_map:: value_type - это пара и lt; const int, std:: shared_ptr > , и компилятор правильно подумает, что пара < int, std:: shared_ptr > не является конвертируемой. Поэтому компилятор выбирает универсальную ссылочную перегрузку, и это разрушает исходный , несмотря на программист, не использующий std:: move(), который является типичным соглашением для указания, что вы в порядке с уничтоженной переменной. Поэтому поведение разрушения вставки на самом деле правильное в соответствии со стандартом С++ 11, а старые компиляторы были неверными.
Вероятно, теперь вы можете увидеть, почему я потратил три дня на диагностику этой ошибки. Это не было совершенно очевидно в большой базе кода, где тип, который был вставлен в unordered_map, был typedef, определенным далеко в терминах исходного кода, и никому не приходило в голову проверить, идентичен ли typedef значению__type.
Итак, мои вопросы для:
-
Почему старые компиляторы не уничтожают переменные, вставленные как более новые компиляторы? Я имею в виду, что даже GCC 4.7 этого не делает, и это соответствует стандартным стандартам.
-
Является ли эта проблема широко известной, поскольку, безусловно, обновление компиляторов приведет к тому, что код, который раньше работал, внезапно прекратил работу?
-
Был ли комитет по стандартам С++ намереваться это поведение?
-
Как вы предлагаете изменить unordered_map:: insert(), чтобы улучшить поведение? Я спрашиваю об этом, потому что, если есть поддержка здесь, я намерен представить это поведение как примечание N к РГ21 и попросить их реализовать лучшее поведение.