Boost logo

Boost :

Subject: Re: [boost] [function] function wrapping with no exceptionsafetyguarantee
From: Daniel Walker (daniel.j.walker_at_[hidden])
Date: 2010-10-21 18:05:44


On Thu, Oct 21, 2010 at 2:25 AM, Doug Gregor <doug.gregor_at_[hidden]> wrote:
> On Wed, Oct 20, 2010 at 1:51 PM, Daniel Walker
> <daniel.j.walker_at_[hidden]> wrote:
>> On Tue, Oct 19, 2010 at 4:56 AM, David Abrahams <dave_at_[hidden]> wrote:
>>> At Tue, 19 Oct 2010 01:06:00 -0700,
>>> Emil Dotchevski wrote:
>>>>
>>>> On Tue, Oct 19, 2010 at 12:34 AM, Domagoj Saric <dsaritz_at_[hidden]> wrote:
>>>> > "Emil Dotchevski" <emil_at_[hidden]> wrote in message
>>>> > news:AANLkTi=1J3+hD0Oh3Le+6-jfnwDLYpTn_A7a6x=oZFnz_at_mail.gmail.com...
>>>> >
>>>> >> ... at worst they'd be mad that you've used
>>>> >> Boost (that's common in games, for example.)
>>>> >
>>>> > Shall we disregard all those cases (of Boost rejection) as irrational rants
>>>> > (as admittedly they often are, be it of the 'corporate policy' type or of
>>>> > the Linus Torvalds type) or shall it be admitted that after all, sometimes,
>>>> > they actually are based on real objections (that Boost, or parts of it, made
>>>> > some not-so-happy efficiency compromising choices)...?
>>>>
>>>> You can't talk about Boost efficiency in general. As difficult as it
>>>> is to pull apart, Boost contains individual components. Are we talking
>>>> about the efficiency of Boost Function then? I'm sure if someone
>>>> manages to speed it up, many people on this mailing list (not to
>>>> mention the folks who are implementing std::function) would be very
>>>> interested to see how it can be done.
>>>
>>> I think we already know one way: we can easily get rid of the separate
>>> empty() check by making sure empty boost::functions all invoke a
>>> function that throws bad_function_call.
>>
>> After taking a closer look at this idea, I don't think it is possible
>> to do this without either changing boost::function's semantics or
>> incurring other runtime expenses.
>>
>> First of all, boost::function can be in an empty state for two
>> reasons: either it has not been assigned a target function or there
>> has been a problem with the internal target management system. Both of
>> these conditions are tested simultaneously by the current empty check
>> in operator(). If we get rid of the empty check, we will no longer be
>> checking that the target management system is in working order and
>> able to dispatch function calls (or more specifically, that
>> boost::function's internal vtable pointer is non-null). If the target
>> management system is not in an usable state, then we cannot dispatch a
>> default "empty" function that throws bad_function_call. So, if we get
>> rid of the current empty check but retain the current target
>> management system, we open the possibility that boost::function could
>> be in an empty state but would not throw when invoked, which would be
>> a change in semantics.
>
> The latter "state" isn't a real state; it only exists if there's a bug
> in boost::function, and we don't design around bugs.

Right. I don't mean the management system has a bug, I mean that it
has encountered a problem, for example, cloning the target. So,
boost::function could be empty because it has never been assigned a
target or because the most recent attempt to assign a target failed...
or because it was cleared by the user calling clear(). Are those all
of the scenarios that can lead to an empty boost::function?

>
>> Alternatively, we could rewrite the target management mechanisms to
>> ensure that boost::function always has a usable vtable object. This
>> implies providing a defualt "empty" vtable, which boost::function's
>> vtable pointer could refer to when in an empty state, so that the
>> pointer is always non-null.
>
> Yes, that's the implementation technique that has been discussed a
> number of times.

Ok, good. I missed out on a lot of those old discussions, so I'm just
now catching up... or trying to, at least. :)

>
>> There would need to be a different "empty"
>> vtable each time boost::function is instantiated with a different call
>> signature, so that the "empty" vtable can be used seamlessly by
>> operator() for any call signature.
>
> One per boost::function signature, yes.
>
>> These "empty" vtable objects could
>> not be allocated on demand, since they would need to be available to
>> boost::function even in the event of heap allocation failures.
>
> They would go into the data segment, as the current vtables do.
>
>> So,
>> adding "empty" vtable objects would increase the space requirements of
>> boost::function; each template instantiation would need two
>> corresponding vtable objects, one for actual targets and one as a
>> fallback that throws bad_function_call. (The "empty" vtable objects
>> could be stored statically, but static storage is also a very precious
>> resource on many platforms.) Not all users would appreciate this
>> trade-off; i.e. an increase in the space overhead for a small decrease
>> in time overhead (with compiler optimization the time savings are
>> minuscule, in my experience).
>
> Without quantifying the trade-off, we don't know which way is better.

Do you have any suggestion for how to quantify this? I've run some
simple benchmarks, but in optimized object code the time overhead of
boost::function is so small it's hard to measure.

The increase in size of the data segment is more straight forward, but
don't we already know it will double? Instead of one vtable object per
signature, there would be two per signature, right?

>
> In any case, this issue is fairly easy to settle; it just takes
> effort. Someone implements this alternative scheme and determines the
> cost/benefit in space and time, and with any luck the choice is
> obvious.

OK, I'm going to spend some more time on this, try to give a working
implementation, and give a report back. Thanks for the help!

Daniel Walker


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