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).
The answer is probably historical:
std::function
is meant as a replacement for function pointers, and function pointers had the capability to beNULL
. So, when you want to offer easy compatibility to function pointers, you need to offer an invalid state.The identifiable invalid state is not really necessary since, as you mentioned,
boost::optional
does that job just fine. So I'd say thatstd::function
's are just there for the sake of history.You just use std::function for callbacks, you can use a simple template helper function that forwards its arguments to the handler if it is not empty:
And use it in the following way:
In the same way that you can add a nullstate to a functor type that doesn't have one, you can wrap a functor with a class that does not admit a nullstate. The former requires adding state, the latter does not require new state (only a restriction). Thus, while i don't know the rationale of the
std::function
design, it supports the most lean & mean usage, no matter what you want.Cheers & hth.,
There are cases where you cannot initialize everything at construction (for example, when a parameter depends on the effect on another construction that in turn depends on the effect on the first ...).
In this cases, you have necessarily to break the loop, admitting an identifiable invalid state to be corrected later. So you construct the first as "null", construct the second element, and reassign the first.
You can, actually, avoid checks, if -where a function is used- you grant that inside the constructor of the object that embeds it, you will always return after a valid reassignment.
One of the most common use cases for
std::function
is to register callbacks, to be called when certain conditions are met. Allowing for uninitialized instances makes it possible to register callbacks only when needed, otherwise you would be forced to always pass at least some sort of no-op function.It does not have an "invalid" state. It is no more invalid than this:
What you have is an empty
function
, just likeaVector
is 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
CallbackFunc
is 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
new
into 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::function
to be empty. Just like we allowstd::vector
to be empty. Just like we allowstd::string
to be empty. Just like we allowstd::shared_ptr
to be empty. And so on.To put it simply:
std::function
contains a function. It is a holder for a callable type. Therefore, there is the possibility that it contains no callable type.