Ho appena perso tre giorni della mia vita rintracciando uno strano bug in cui unordered_map :: insert () distrugge la variabile che inserisci. Questo comportamento altamente non ovvio si verifica solo in compilatori molto recenti: ho scoperto che clang 3.2-3.4 e GCC 4.8 sono gli unici compilatori a dimostrare questa "caratteristica".
Ecco un po 'di codice ridotto dalla mia base di codice principale che dimostra il problema:
#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;
}
Io, come probabilmente la maggior parte dei programmatori C ++, mi aspetto che l'output assomigli a questo:
a.second is 0x8c14048
a.second is now 0x8c14048
Ma con clang 3.2-3.4 e GCC 4.8 ottengo invece questo:
a.second is 0xe03088
a.second is now 0
Il che potrebbe non avere senso, fino a quando non esaminerai attentamente i documenti per unordered_map :: insert () su http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/ dove l'overload n. 2 è:
template <class P> pair<iterator,bool> insert ( P&& val );
Che è un avido sovraccarico di movimento di riferimento universale, che consuma tutto ciò che non corrisponde a nessuno degli altri sovraccarichi e si sposta costruendolo in un valore_tipo. Allora perché il nostro codice sopra ha scelto questo sovraccarico e non il sovraccarico unordered_map :: value_type come probabilmente la maggior parte si aspetterebbe?
La risposta ti guarda in faccia: unordered_map :: value_type è una coppia < const int, std :: shared_ptr> e il compilatore penserebbe correttamente che una coppia < int , std :: shared_ptr> non è convertibile. Pertanto il compilatore sceglie il sovraccarico del riferimento universale di spostamento, e questo distrugge l'originale, nonostante il programmatore non utilizzi std :: move () che è la tipica convenzione per indicare che sei a posto con una variabile che viene distrutta. Pertanto, il comportamento di distruzione dell'inserimento è di fatto corretto secondo lo standard C ++ 11 ei compilatori più vecchi non erano corretti .
Probabilmente ora puoi vedere perché ho impiegato tre giorni per diagnosticare questo bug. Non era affatto ovvio in una grande base di codice in cui il tipo da inserire in unordered_map era un typedef definito lontano in termini di codice sorgente, e non è mai venuto in mente a nessuno di controllare se il typedef fosse identico a value_type.
Quindi le mie domande a Stack Overflow:
Perché i compilatori più vecchi non distruggono le variabili inserite come i compilatori più recenti? Voglio dire, anche GCC 4.7 non lo fa ed è piuttosto conforme agli standard.
Questo problema è ampiamente noto, perché sicuramente l'aggiornamento dei compilatori farà smettere improvvisamente di funzionare il codice che prima funzionava?
Il comitato per gli standard C ++ intendeva questo comportamento?
Come suggeriresti di modificare unordered_map :: insert () per dare un comportamento migliore? Lo chiedo perché se qui c'è supporto, intendo inviare questo comportamento come una nota N al WG21 e chiedere loro di implementare un comportamento migliore.
4.9.0 20131223 (experimental)
rispettivamente gcc 4.8.2 e . L'output è a.second is now 0x2074088
(o simile) per me.
a
non lo è. Dovrebbe fare una copia. Inoltre, questo comportamento dipende totalmente dallo stdlib, non dal compilatore.