Once upon a time, there was an active object which had callbacks using std::function.
On the same day, a user of this object registered a callback function which, in its core, cancelled the registration. That callback was a lambda that aside of that took some reference in the current context.
And guess what happened ?
Here is the code that demonstrates that « fairy tale »:
#include <functional>
#include <iostream>
struct Object {
using Handler = std::function<void (Object&)>;
Handler handler;
void setHandler(Handler f) {
handler = f;
}
void invokeHandler() {
if (handler) {
handler(*this);
}
}
};
int main()
{
uint32_t v = 42;
Object o;
o.setHandler([&v](Object& obj) {
std::cout << "before:" << v << std::endl;
obj.setHandler(nullptr);
std::cout << "after: " << v << std::endl;
});
o.invokeHandler();
}
Langage du code : PHP (php)
In its current form, on Linux and on coliru, it doesn’t crash but « after » does not show 42 anymore, only a random number…
Why ?
When the callback invokes setHandler(nullptr)
, it clears the std::function. As that std::function embedded a copy of the capture of the lambda, clearing the std::function frees the context. As such, the reference to v
is now part of a freed block. When the capture is small enough, the corresponding pointer becomes random. When the capture is larger (two uint32_t are sufficient), accessing v
in the « after » line is sufficient to cause a segmentation fault.
Hint: valgrind raises an invalid read error. It is unfortunately hard to link it to the copy/clear of the lambda capture in std::function context.
In fact, even when a lambda does capture by reference, the capture is really like a struct containing a reference. Access to the referenced variable is not direct, it always goes through the capture. Such that deleting the std::function deletes the capture and kills the references as well as the copied values.