Boost logo

Boost :

Subject: Re: [boost] [function] function wrapping with no exceptionsafetyguarantee
From: Daniel Walker (daniel.j.walker_at_[hidden])
Date: 2010-10-22 13:35:48

On Thu, Oct 21, 2010 at 7:05 PM, Daniel Walker
<daniel.j.walker_at_[hidden]> wrote:
> On Thu, Oct 21, 2010 at 6:13 PM, Edward Diener <eldiener_at_[hidden]> wrote:
>> On 10/21/2010 6:05 PM, Daniel Walker wrote:
>>> 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?
>> I don't mean to question the design of boost::function but wouldn't the
>> inability of cloning a target, or assign a target, be a problem which should
>> lead to an exception being thrown ?
> Right, but suppose boost::function was instantiated outside of the try
> block where the assignment fails. Then after the exception is thrown
> and handled, the boost::function object could still be used.
> But actually, I just noticed that, even though the portable function
> wrappers are empty after an assignment failure, boost::function is not
> actually cleared. So, in fact, a failed assignment is NOT a scenario
> that leads to an empty state, as I first thought. This is probably an
> oversight/bug in the portable wrappers, right?
>> I would assume that boost::function not having a target would normally only
>> occur if no target had been set or if the user removed a target which had
>> been set.
> Right, and indeed, this appears to be the case... unless there's some
> other scenario we overlooked...

Oops. Sorry, I spoke too soon. In fact, it IS currently possible for a
boost::function object to become empty due to a failed assignment. It
happens because the small object manager clones the target during a
call to swap(). If there is an exception during the allocation,
boost::function handles it, sets itself to empty and rethrows. Here's
an example that demonstrates the behavior.

#include <cassert>
#include <iostream>
#include <boost/function.hpp>

struct S0 {
    void operator()() {}

int i = 0;
struct S1 {
    void operator()() {}
    void* operator new(std::size_t, void*)
        // throw on third alloc
        if(++i == 3)
            throw std::bad_alloc();

int main()
    boost::function<void()> g = S0();
    assert(g); // assertion ok, since g is not empty.
    try {
        g = S1();
    catch(std::exception& e) {
        std::cerr << "failed function assignment: "
                  << e.what() << std::endl;
    assert(g); // now assert fails, since g is empty.
    return 0;

Here's a backtrace just before the bad_alloc is thrown.

#0 S1::operator new () at function_assignment_test.cpp:15
#1 0x00000001000019e1 in
(in_buffer=@0x7fff5fbff298, out_buffer=@0x7fff5fbff378,
op=boost::detail::function::move_functor_tag) at function_base.hpp:318
#2 0x0000000100001abf in
(in_buffer=@0x7fff5fbff298, out_buffer=@0x7fff5fbff378,
op=boost::detail::function::move_functor_tag) at function_base.hpp:364
#3 0x0000000100001aeb in
(in_buffer=@0x7fff5fbff298, out_buffer=@0x7fff5fbff378,
op=boost::detail::function::move_functor_tag) at function_base.hpp:412
#4 0x0000000100001b44 in
(in_buffer=@0x7fff5fbff298, out_buffer=@0x7fff5fbff378,
op=boost::detail::function::move_functor_tag) at function_base.hpp:440
#5 0x0000000100001d25 in boost::function0<void>::move_assign
(this=0x7fff5fbff370, f=@0x7fff5fbff290) at function_template.hpp:974
#6 0x0000000100001dc7 in boost::function0<void>::swap
(this=0x7fff5fbff300, other=@0x7fff5fbff370) at
#7 0x0000000100001e2a in boost::function<void ()()>::operator=<S1>
(this=0x7fff5fbff370, f={<No data fields>}) at
#8 0x0000000100001221 in main () at function_assignment_test.cpp:25

Daniel Walker

Boost list run by bdawes at, gregod at, cpdaniel at, john at