Aujourd'hui, nous avons découvert la cause d'un vilain bug qui ne s'est produit que par intermittence sur certaines plates-formes. En résumé, notre code ressemblait à ceci:
class Foo {
map<string,string> m;
void A(const string& key) {
m.erase(key);
cout << "Erased: " << key; // oops
}
void B() {
while (!m.empty()) {
auto toDelete = m.begin();
A(toDelete->first);
}
}
}
Le problème peut sembler évident dans ce cas simplifié: B
passe une référence à la clé à A
, qui supprime l’entrée de la carte avant de tenter de l’imprimer. (Dans notre cas, il n'a pas été imprimé, mais utilisé d'une manière plus compliquée.) Il s'agit bien sûr d'un comportement indéfini, car il key
s'agit d'une référence suspendue après l'appel à erase
.
La résolution de ce problème était triviale - nous venons de changer le type de paramètre de const string&
à string
. La question est: comment aurions-nous pu éviter ce bug en premier lieu? Il semble que les deux fonctions ont fait le bon choix:
A
n'a aucun moyen de savoir quikey
fait référence à la chose qu'il est sur le point de détruire.B
aurait pu en faire une copie avant de la transmettre àA
, mais l’appelé n’est-il pas de décider des paramètres à prendre par valeur ou par référence?
Y a-t-il une règle que nous n'avons pas suivie?