Oggi abbiamo scoperto la causa di un brutto bug che si è verificato solo saltuariamente su determinate piattaforme. Bollito, il nostro codice assomigliava a questo:
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);
}
}
}
Il problema potrebbe sembrare ovvio in questo caso semplificato: B
passa un riferimento alla chiave a A
, che rimuove la voce della mappa prima di tentare di stamparla. (Nel nostro caso, non è stato stampato, ma usato in un modo più complicato) Questo è ovviamente un comportamento non definito, poiché key
è un riferimento ciondolante dopo la chiamata a erase
.
Risolvere ciò è stato banale - abbiamo appena cambiato il tipo di parametro da const string&
a string
. La domanda è: come abbiamo potuto evitare questo bug in primo luogo? Sembra che entrambe le funzioni abbiano fatto la cosa giusta:
-
A
non ha modo di sapere chekey
si riferisce alla cosa che sta per distruggere. -
B
potrebbe aver fatto una copia prima di passarla aA
, ma non è compito del callee decidere se prendere parametri per valore o per riferimento?
C'è qualche regola che non siamo riusciti a seguire?