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

Chr*_*ris 5 c++ boost boost-bind boost-function

说我有这样的功能:

void someFunction(const ExpensiveObjectToCopy&);
Run Code Online (Sandbox Code Playgroud)

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

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

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

boost::function<void()> f2 = f;
callSomeFunction(f);
etc.
Run Code Online (Sandbox Code Playgroud)

Gri*_*zly 4

据我所知(从对不完全简单的源代码和一些实验的粗略阅读来看),它每次都会复制克隆的对象。如果函数通过 const & 获取参数,则可能没有必要,但通常该对象可能会被函数改变。boost::ref如果对象的复制成本很高,那么通过引用(或想到)捕获它是否有意义,boost::cref或者,如果原始对象在调用时不存在,则捕获boost::shared_ptr并编写一个适配器方法,该方法解压 smartpointer 并调用someFunction?

编辑:根据实验,它不仅会在boost::function复制时复制构造该对象,而且还会在内部复制多次boost::bind。我在 mingw 32 下使用 boost 1.45 和 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;
}
Run Code Online (Sandbox Code Playgroud)

结果输出如下:

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
Run Code Online (Sandbox Code Playgroud)

因此,复制构造函数被调用一次来创建 f2,并调用了 11 次来绑定和赋值给 f1。由于第一个对象是在堆栈上创建的,并且副本的地址非常接近并且略有增加,因此绑定过程似乎经历了很多函数,在这种情况下编译器不会内联这些函数,并且每个函数按值传递对象。仅使用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
Run Code Online (Sandbox Code Playgroud)

所以五个副本只是为了绑定对象。因此,我通常会避免捕获任何在代码的任何远程性能敏感部分中至少具有中等复制成本的值。相比之下,gcc 的性能std::tr1::bindstd::bind好得多(与 std::tr1::function / std::function 结合使用)(代码基本上与第一个测试代码相同,只需分别替换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
Run Code Online (Sandbox Code Playgroud)

我假设std::bind要么通过 const ref 进行内部调用,要么以对 gccs 内联器更友好的方式编写,以内联一些内容并消除冗余的复制构造函数。tr1::bind那么仍然会得到更好的优化boost::bind,但仍远未达到最佳状态。

当然,与往常一样,使用不同的编译标志/编译器进行此类测试 YMMV