
On Wed, Oct 13, 2010 at 5:57 PM, Nevin Liber <nevin@eviloverlord.com> wrote:
On 13 October 2010 15:35, Daniel Walker <daniel.j.walker@gmail.com> wrote:
If the call precondition is not met and boost::function::operator() attempts to call the target function, then the program could crash.
In the case of raw function pointers (either NULL or uninitialized), from that point on the program can do whatever the heck it wants to do, since it is now in the realm of undefined behavior. Heck, you can no longer guarantee that *any* object in your system is still in a consistent, let alone correct state.
True, and in the worst case scenario, the program could crash.
Instead, under the current implementation, boost::function::operator() checks the call precondition
What call precondition? It is perfectly legitimate to call operator() on a default constructed boost::function; it has well defined semantics.
In order to call a function using boost::function, the following precondition must be met: boost::function must be non-empty. The reason that operator() has well defined semantics, even when called on a default constructed boost::function, is that operator() checks the call precondition before attempting to dispatch a target function.
Now in your new class you want to change those well defined semantics into undefined behavior. But "undefined behavior" means that the program can do whatever the heck it wants, including the currently defined behavior for calling operator() on a default constructed boost::function.
From the implementers point of view, yes, "undefined behavior" means
that the program could be given any particular behavior. But from the users' point of view, "undefined behavior" means that no particular behavior can be relied upon; i.e. there is no contract between the implementer and user as to how the function behaves. I think I should have emphasized earlier that unsafe_function is not "unsafe" in general, but is only unsafe relative to boost::function; i.e. given the same exception safety preconditions, the two do not have the same exception safety guarantee. However, given that the call precondition is met, i.e. the function wrapper is non-empty, then both are strong exception safe. I'm going to update the user documentation to make sure this is clear. BTW, I think some people don't like the name "unsafe," but I still prefer it. I think it's important to emphasis the distinction between the two wrappers. boost::function behaves as one might expect a function object to behave: invoking the target function happens seamlessly without requiring the user to become familiar with any new exception safety preconditions above and beyond the target function's preconditions. However, unsafe_function, under the same exception safety preconditions, does not behave the same way, and using it naively could cause an abrupt system failure. So, following the principle of least astonishment, I like the name unsafe_function as a warning to the user to be aware of the different exception safety preconditions of the two wrappers.
For a parallel, look at std::vector at() vs. operator[]. at() has no precondition on the index passed to it, while operator[] requires it to be in the range [0..size()).
Would you say that operator[] doesn't have the strong exception safety guarantee?
On the precondition that the vector is not empty, then operator[] has a strong exception safety guarantee. On the other hand, at() is unconditionally strong exception safe. It's just a matter of stating what the preconditions are for exception safety. boost::function has no exception safety precondition, unsafe_function has one exception safety precondition -- the call precondition.
and either completes successfully or throws an exception with the program state maintained, which conforms to our running definition of a strong exception safety guarantee.
Exception safety is an orthogonal issue, as you are talking about changing well defined semantics into undefined behavior. Undefined behavior in and of itself just isn't a motivation for any feature. And you aren't (just) relaxing the exception safety guarantees; rather, you are throwing them (as well as any other guarantees) out by invoking undefined behavior.
Undefined behavior is not the motivation. Actually, maybe it's a good idea to walk through how I arrived at this function wrapper to begin with, step-by-step. 1) The motivation is to remove the call to boost::throw_exception in operator(). 2) If boost::throw_exception is never used, then there's no reason to check the call precondition in operator(). In fact, the user request specifically emphasized that the empty check was unnecessary. 3) If you remove the empty check from operator(), how would you describe the resulting function's behavior to the user? Well, the behavior cannot always be defined, since it sometimes involves dereferencing a null pointer. Moreover, given the same exception safety preconditions as boost::function, it does not conform to the definition of basic, strong or nothrow exception safety. So, the resulting operator() is exception unsafe under the same exception safety preconditions as boost::function. 4) Hey, that's a good name! I'll call it unsafe_function. Daniel Walker