复制boost :: function是否也复制了闭包?

时间:2011-12-31 14:37:39

标签: c++ boost boost-bind boost-function

说我有这样的功能:

void someFunction(const ExpensiveObjectToCopy&);

如果我将boost :: function输出,那么该函数将在其闭包中存储自己的克隆副本:

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

现在,如果我开始传递f,那么boost :: function复制构造函数每次都会复制该对象,还是每个函数共享相同的闭包? (即像这样)

boost::function<void()> f2 = f;
callSomeFunction(f);
etc.

2 个答案:

答案 0 :(得分:4)

从我能找到的东西(从粗略阅读不完全简单的来源和一些实验来判断)它将每次复制克隆的对象。在函数接受const&amp;的参数的情况下可能是不必要的,但通常对象可能会被函数变异。如果复制对象的代价很​​高,那么通过引用捕获它是不合理的(boost::refboost::cref会想到)或者,如果原始对象在调用时不存在,捕获boost::shared_ptr并编写一个适配器方法,解压缩smartpointer并调用someFunction

编辑:从实验开始,每当复制boost::function时,它不仅会复制构造该对象,而且还会在boost::bind内复制多次。我使用下面的代码使用boost 1.45在mingw 32下使用gcc 4.6和-O2(以及-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;
}

结果输出如下:

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

因此,为了绑定和赋值给f1,调用了复制构造函数来创建f2一次和11次。由于第一个对象是在堆栈上创建的,并且副本的地址非常接近并稍微增加,因此绑定过程似乎经历了很多函数,编译器在这种情况下不会内联,每个函数都是按值传递对象。仅使用boost::bind而不将结果保存在任何位置:

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

所以只有五个副本来绑定对象。因此,我通常会避免捕获任何在代码的任何远程性能敏感部分中每个值至少具有中等复制成本的内容。相比之下,gccs std::tr1::bindstd::bind执行得更好(与std :: tr1 :: function / std :: function一起)(代码基本上与第一个测试代码相同,只需替换{{1} } boost::分别std::tr1::

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 要么通过const ref进行内部调用,要么以对gccs inliner更友好的方式编写,以内联一些并消除冗余的副本构造函数。 std::bind仍然比tr1::bind更好地优化,但仍然不是最优的。

当然一如既往地使用不同的编译标志/编译器进行YMMV这样的测试

答案 1 :(得分:3)

如果您将对象按值传递给bind,则会复制它(当您测试时:11次)。

但是如果您不想制作副本然后通过引用传递(使用boost::cref)并且不会被复制。

struct a
{
    a() { std::cout << __func__ << std::endl; }
    a(const a &) { std::cout << __func__ << std::endl; }
    ~a() { std::cout << __func__ << std::endl; }
    const a & operator=(const a & aa)
    { std::cout << __func__ << std::endl; return aa; }
};

void g(const a &) { std::cout << __func__ << std::endl; }


void t2()
{
    a aa;

    boost::function< void() > ff = boost::bind(g, boost::cref(aa));
    boost::function< void() > ff2 = ff;
    std::cout << "after ff" << std::endl;
    ff();
    ff2();
    std::cout << "after called ff()" << std::endl;
}

输出:

a
after ff
g
g
after called ff()
~a

那是

  1. 在创建对象时调用的一个构造函数
  2. 创建函数对象ff或复制它(ff2)时没有调用构造函数
  3. 通过g(const a &)ff()
  4. 调用ff2()时没有调用构造函数
  5. 当对象超出范围时调用的析构函数