Hoy descubrimos la causa de un error desagradable que solo ocurrió de manera intermitente en ciertas plataformas. En resumen, nuestro código se veía así:
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);
}
}
}
El problema puede parecer obvio en este caso simplificado: B
pasa una referencia a la clave A
, que elimina la entrada del mapa antes de intentar imprimirla. (En nuestro caso, no se imprimió, sino que se usó de una manera más complicada) Esto es, por supuesto, un comportamiento indefinido, ya que key
es una referencia pendiente después de la llamada a erase
.
Arreglar esto fue trivial: simplemente cambiamos el tipo de parámetro de const string&
a string
. La pregunta es: ¿cómo podríamos haber evitado este error en primer lugar? Parece que ambas funciones hicieron lo correcto:
A
no tiene forma de saber que sekey
refiere a lo que está a punto de destruir.B
podría haber hecho una copia antes de pasarlaA
, pero ¿no es el trabajo del destinatario decidir si tomar los parámetros por valor o por referencia?
¿Hay alguna regla que no pudimos seguir?