5

假设我有这样的功能:

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.
4

2 回答 2

4

从我能找到的(从对不完全简单的来源和一些实验的粗略阅读来看)它每次都会复制克隆的对象。如果函数通过 const & 获取参数,则可能没有必要,但通常该对象可能会被函数突变。boost::ref如果对象的复制成本很高,那么通过引用(或boost::cref想到)捕获它是否有意义,或者,如果原始对象在调用点不存在,则捕获boost::shared_ptr并编写一个适配器方法,该方法解压智能指针并调用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;
}

结果输出如下:

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

因此,复制构造函数被调用一次以创建 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

所以五个副本只是为了绑定对象。因此,我通常会避免在代码的任何远程性能敏感部分中捕获至少具有中等复制成本的任何内容。相比之下 gccsstd::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

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

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

于 2011-12-31T15:00:01.013 回答
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()或调用时不调用构造函数ff2()
  4. 当对象超出范围时调用析构函数
于 2011-12-31T15:29:09.503 回答