Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does copying a boost::function also copy the closure?

Say I have a function like this:

void someFunction(const ExpensiveObjectToCopy&);

If I make a boost::function out if it, that function will store its own cloned copy of the object in its closure:

boost::function<void()> f = boost::bind(someFunction, x);  // <-- f saves a copy of x

Now if I start passing f around, will the boost::function copy constructor copy that object again each time, or does each function share the same closure? (i.e. like this)

boost::function<void()> f2 = f;
callSomeFunction(f);
etc.
like image 575
Chris Avatar asked Dec 31 '11 14:12

Chris


1 Answers

From what I can find (Judging from a cursory reading of the not exactly simple source and some experiments) it will copy the cloned object each time. It might be unnecessary in case of the function taking it's argument by const &, but in general the object might be mutated by the function. If the object is expensive to copy, wouldn't it make sense to capture it by reference (boost::ref or boost::cref come to mind) or, if the original object doesn't exist at the point of invocation, capture a boost::shared_ptr and write a adapter method, which unpacks the smartpointer and calls someFunction?

Edit: From experimentation not only will it copy construct that object whenever the boost::function is copied, but it will also copy several times inside boost::bind. I tested using the following code using boost 1.45 under mingw 32 with gcc 4.6 and -O2 ( and -std=c++0x):

struct foo_bar {
    std::vector<int> data; //possibly expensive to copy
    foo_bar()
    { std::cout<<"default foo_bar "<<std::endl; }
    foo_bar(const foo_bar& b):data(b.data)
    {  std::cout<<"copy foo_bar "<<&b<<" to "<<this<<std::endl; }
    foo_bar& operator=(const foo_bar& b) {
        this->data = b.data;
        std::cout<<"asign foo_bar "<<&b<<" to "<<this<<std::endl;
        return *this;
    }
    ~foo_bar(){}
};

void func(const foo_bar& bar) { std::cout<<"func"<<std::endl;}

int main(int, char*[]) {
    foo_bar fb;
    boost::function<void()> f1(boost::bind(func, fb));
    std::cout<<"Bind finished"<<std::endl;
    boost::function<void()> f2(f1);
    std::cout<<"copy finished"<<std::endl;
    f1();
    f2();
    return 0;
}

The resulting output is as followed:

default foo_bar
copy foo_bar 0x28ff00 to 0x28ff10
copy foo_bar 0x28ff10 to 0x28ff28
copy foo_bar 0x28ff28 to 0x28ff1c
copy foo_bar 0x28ff1c to 0x28ff34
copy foo_bar 0x28ff34 to 0x28fed4
copy foo_bar 0x28fed4 to 0x28fee4
copy foo_bar 0x28fee4 to 0x28fef4
copy foo_bar 0x28fef4 to 0x28fe14
copy foo_bar 0x28fe14 to 0x28fe24
copy foo_bar 0x28fe24 to 0x28fe34
copy foo_bar 0x28fe34 to 0x6a2c7c
Bind finished
copy foo_bar 0x6a2c7c to 0x6a2c94
copy finished
func
func

So the copy constructor was called for the creation of f2 once and 11 times for the binding and assignment to f1. Since the first object is created on the stack and the addresses of the copies are very close to that and slightly increasing, it seems that the binding process goes through a lot of functions, which the compiler doesn't inline in this case and which each pass the object along by value. Using just boost::bind without saving the result anywhere:

int main(int, char*[]) {
    foo_bar fb;
    boost::function<void()> f1(boost::bind(func, fb));
    return 0;
}

default foo_bar
copy foo_bar 0x28ff00 to 0x28ff10
copy foo_bar 0x28ff10 to 0x28ff28
copy foo_bar 0x28ff28 to 0x28ff1c
copy foo_bar 0x28ff1c to 0x28ff34
copy foo_bar 0x28ff34 to 0x28fef4

So five copies just to bind the object. So I would generaly avoid capturing anything which has at least moderate copy costs per value in any even remotely performance sensitive parts of the code. In comparison gccs std::tr1::bind and std::bind perform much better (in conjunction with std::tr1::function / std::function) (code is basically identical to the first testcode, just substitute boost:: with std::tr1:: respectively std:::

std::tr1::bind with std::tr1::function:
default foo_bar
copy foo_bar 0x28ff10 to 0x28ff28
copy foo_bar 0x28ff28 to 0x28ff34
copy foo_bar 0x28ff34 to 0x28ff04
copy foo_bar 0x28ff04 to 0x652c7c
Bind finished
copy foo_bar 0x652c7c to 0x652c94
copy finished
func
func

std::bind with std::function:
default foo_bar
copy foo_bar 0x28ff34 to 0x28ff28
copy foo_bar 0x28ff28 to 0x3c2c7c
Bind finished
copy foo_bar 0x3c2c7c to 0x3c2c94
copy finished
func
func

I assume std::bind either passes by const ref for internal invocations, or is written in a way which is more friendly to gccs inliner to inline some in and eliminate redundant copy constructors. tr1::bind still gets better optimized then boost::bind, but still far from optimal.

Of course as always with such a kind of tests YMMV with different compile flags/compilers

like image 154
Grizzly Avatar answered Oct 03 '22 03:10

Grizzly