Właśnie straciłem trzy dni mojego życia, szukając bardzo dziwnego błędu, w którym unordered_map :: insert () niszczy wstawioną zmienną. To wysoce nieoczywiste zachowanie występuje tylko w najnowszych kompilatorach: stwierdziłem, że Clang 3.2-3.4 i GCC 4.8 są jedynymi kompilatorami, które zademonstrowały tę „funkcję”.
Oto trochę zredukowany kod z mojej głównej bazy kodu, który demonstruje problem:
#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;
}
Ja, podobnie jak prawdopodobnie większość programistów C ++, spodziewałbym się, że wynik będzie wyglądał mniej więcej tak:
a.second is 0x8c14048
a.second is now 0x8c14048
Ale z clang 3.2-3.4 i GCC 4.8 otrzymuję to:
a.second is 0xe03088
a.second is now 0
Co może nie mieć sensu, dopóki nie przyjrzysz się dokładnie dokumentom dla unordered_map :: insert () pod adresem http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/, gdzie przeciążenie nr 2 to:
template <class P> pair<iterator,bool> insert ( P&& val );
Co jest chciwym, uniwersalnym przeciążeniem ruchu referencyjnego, zużywającym wszystko, co nie pasuje do żadnego z innych przeciążeń, i przenosząc konstruowanie go do value_type. Dlaczego więc nasz kod powyżej wybrał to przeciążenie, a nie przeciążenie unordered_map :: value_type, jak prawdopodobnie większość się spodziewała?
Odpowiedź patrzy ci prosto w twarz: unordered_map :: value_type to para < const int, std :: shared_ptr> i kompilator poprawnie pomyśli, że para < int , std :: shared_ptr> nie jest konwertowalna. Dlatego kompilator wybiera przeciążenie odwołania uniwersalnego przenoszenia, co niszczy oryginał, mimo że programista nie używa std :: move (), co jest typową konwencją wskazującą, że nie ma problemu z niszczeniem zmiennej. Dlatego zachowanie niszczenia wstawek jest w rzeczywistości poprawne, zgodnie ze standardem C ++ 11, a starsze kompilatory były niepoprawne .
Prawdopodobnie teraz widzisz, dlaczego zdiagnozowanie tego błędu zajęło mi trzy dni. Nie było to wcale oczywiste w dużej bazie kodu, gdzie typ wstawiany do unordered_map był typedef zdefiniowanym daleko w warunkach kodu źródłowego i nigdy nie przyszło nikomu do głowy, aby sprawdzić, czy typedef jest identyczny z value_type.
Więc moje pytania do Stack Overflow:
Dlaczego starsze kompilatory nie niszczą zmiennych wstawionych jak nowsze kompilatory? To znaczy, nawet GCC 4.7 tego nie robi i jest dość zgodne ze standardami.
Czy ten problem jest powszechnie znany, ponieważ z pewnością aktualizacja kompilatorów spowoduje, że kod, który działał, nagle przestanie działać?
Czy komitet normalizacyjny C ++ miał taki zamiar?
Jak sugerowałbyś modyfikację unordered_map :: insert (), aby zapewnić lepsze zachowanie? Pytam o to, ponieważ jeśli jest tu wsparcie, zamierzam przesłać to zachowanie jako notę N do WG21 i poprosić o zaimplementowanie lepszego zachowania.
4.9.0 20131223 (experimental)
odpowiednio gcc 4.8.2 i . Wynik jest a.second is now 0x2074088
(lub podobny) dla mnie.
a
nie jest zwykłe . Powinien zrobić kopię. Ponadto to zachowanie całkowicie zależy od standardowej biblioteki, a nie od kompilatora.