Hoy descubrimos la causa de un error desagradable que solo sucedió de forma intermitente en ciertas plataformas. Reducido, 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 A
, lo que elimina la entrada del mapa antes de intentar imprimirlo. (En nuestro caso, no se imprimió, pero 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
.
La solución de este problema fue trivial: solo 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 quekey
se refiere a lo que está por destruir. -
B
podría haber hecho una copia antes de pasarla aA
, pero ¿no es el trabajo del que llama decidir si tomar parámetros por valor o por referencia?
¿Hay alguna regla que no hayamos seguido?