Boost logo

Boost :

Subject: Re: [boost] [function] function wrapping with no exception safetyguarantee
From: Daniel Walker (daniel.j.walker_at_[hidden])
Date: 2010-10-20 18:51:54


On Wed, Oct 13, 2010 at 5:57 PM, Nevin Liber <nevin_at_[hidden]> wrote:
> On 13 October 2010 15:35, Daniel Walker <daniel.j.walker_at_[hidden]> 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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk