Why do std::function instances have a default cons

2019-01-25 06:20发布

问题:

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).

回答1:

True, but this is also true for other types. E.g. if I want my class to have an optional Person, then I make my data member a Person-pointer. Why not do the same for std::functions? What is so special about std::function that it can have an 'invalid' state?

It does not have an "invalid" state. It is no more invalid than this:

std::vector<int> aVector;
aVector[0] = 5;

What you have is an empty function, just like aVector is an empty vector. The object is in a very well-defined state: the state of not having data.

Now, let's consider your "pointer to function" suggestion:

void CallbackRegistrar(..., std::function<void()> *pFunc);

How do you have to call that? Well, here's one thing you cannot do:

void CallbackFunc();
CallbackRegistrar(..., CallbackFunc);

That's not allowed because CallbackFunc is a function, while the parameter type is a std::function<void()>*. Those two are not convertible, so the compiler will complain. So in order to do the call, you have to do this:

void CallbackFunc();
CallbackRegistrar(..., new std::function<void()>(CallbackFunc));

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:

void CallbackRegistrar(..., std::shared_ptr<std::function<void()>> pFunc);

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 allow std::vector to be empty. Just like we allow std::string to be empty. Just like we allow std::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.



回答2:

Actually, your application should not crash.

§ 20.8.11.1 Class bad_function_call [func.wrap.badcall]

1/ An exception of type bad_function_call is thrown by function::operator() (20.8.11.2.4) when the function wrapper object has no target.

The behavior is perfectly specified.



回答3:

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.



回答4:

The answer is probably historical: std::function is meant as a replacement for function pointers, and function pointers had the capability to be NULL. 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 that std::function's are just there for the sake of history.



回答5:

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.



回答6:

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.,



回答7:

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:

template <typename Callback, typename... Ts>
void SendNotification(const Callback & callback, Ts&&... vs)
{
    if (callback)
    {
        callback(std::forward<Ts>(vs)...);
    }
}

And use it in the following way:

std::function<void(int, double>> myHandler;
...
SendNotification(myHandler, 42, 3.15);