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頭に浮かぶ) のは理にかなっていないでしょうか。または、元のオブジェクトが呼び出しの時点で存在しない場合は、a をキャプチャしboost::shared_ptrてアダプタ メソッドを記述します。 smartpointer を解凍してsomeFunction?

編集:実験から、 がコピーされるたびにそのオブジェクトをコピーするだけでなくboost::function、 内で数回コピーしますboost::bind。gcc 4.6 および -O2 (および -std=c++0x) で mingw 32 の下でブースト 1.45 を使用して、次のコードを使用してテストしました。

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 の作成のために 1 回呼び出され、f1 へのバインディングと代入のために 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

したがって、オブジェクトをバインドするためだけに 5 つのコピーが必要です。したがって、コードのパフォーマンスに敏感な部分であっても、値あたりのコピーコストが少なくとも中程度のものをキャプチャすることは一般的に避けます。比較すると、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. オブジェクトが作成されたときに呼び出される1つのコンストラクタ
  2. 関数オブジェクトの作成時ffまたはそのコピーの作成時にコンストラクターは呼び出されません(ff2
  3. またはg(const a &)を介して呼び出されたときにコンストラクタが呼び出されないff()ff2()
  4. オブジェクトがスコープから外れたときに呼び出されるデストラクタ
于 2011-12-31T15:29:09.503 に答える