Today we found out the cause of a nasty bug that only happened intermittently on certain platforms. Boiled down, our code looked like this:
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);
}
}
}
The problem might seem obvious in this simplified case: B
passes a reference to the key to A
, which removes the map entry before attempting to print it. (In our case, it wasn't printed, but used in a more complicated way) This is of course undefined behavior, since key
is a dangling reference after the call to erase
.
Fixing this was trivial - we just changed the parameter type from const string&
to string
. The question is: how could we have avoided this bug in the first place? It seems both functions did the right thing:
A
has no way of knowing thatkey
refers to the thing it's about to destroy.B
could have made a copy before passing it toA
, but isn't it the callee's job to decide whether to take parameters by value or by reference?
Is there some rule we failed to follow?