This is probably a philosophical question, but I ran into the following problem:
If you define an std::function, and you don’t initialize it correctly, your application will crash, like this:
typedef std::function<void(void)> MyFunctionType;
MyFunctionType myFunction;
myFunction();
If the function is passed as an argument, like this:
void DoSomething (MyFunctionType myFunction)
{
myFunction();
}
Then, of course, it also crashes. This means that I am forced to add checking code like this:
void DoSomething (MyFunctionType myFunction)
{
if (!myFunction) return;
myFunction();
}
Requiring these checks gives me a flash-back to the old C days, where you also had to check all pointer arguments explicitly:
void DoSomething (Car *car, Person *person)
{
if (!car) return; // In real applications, this would be an assert of course
if (!person) return; // In real applications, this would be an assert of course
...
}
Luckily, we can use references in C++, which prevents me from writing these checks (assuming that the caller didn’t pass the contents of a nullptr to the function:
void DoSomething (Car &car, Person &person)
{
// I can assume that car and person are valid
}
So, why do std::function instances have a default constructor? Without default constructor you wouldn’t have to add checks, just like for other, normal arguments of a function.
And in those ‘rare’ cases where you want to pass an ‘optional’ std::function, you can still pass a pointer to it (or use boost::optional).
It does not have an “invalid” state. It is no more invalid than this:
What you have is an empty
function, just likeaVectoris an emptyvector. The object is in a very well-defined state: the state of not having data.Now, let’s consider your “pointer to function” suggestion:
How do you have to call that? Well, here’s one thing you cannot do:
That’s not allowed because
CallbackFuncis a function, while the parameter type is astd::function<void()>*. Those two are not convertible, so the compiler will complain. So in order to do the call, you have to do this:You have just introduced
newinto the picture. You have allocated a resource; who is going to be responsible for it?CallbackRegistrar? Obviously, you might want to use some kind of smart pointer, so you clutter the interface even more with:That’s a lot of API annoyance and cruft, just to pass a function around. The simplest way to avoid this is to allow
std::functionto be empty. Just like we allowstd::vectorto be empty. Just like we allowstd::stringto be empty. Just like we allowstd::shared_ptrto be empty. And so on.To put it simply:
std::functioncontains a function. It is a holder for a callable type. Therefore, there is the possibility that it contains no callable type.