2011-12-31 48 views
5

說我有這樣的功能:複製boost :: function是否也複製閉包?

void someFunction(const ExpensiveObjectToCopy&); 

如果我做一個boost ::功能,如果它,函數將對象的自己的克隆副本存儲在其關閉:

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

現在,如果我開始傳遞f,boost :: function copy構造函數會每次再次複製該對象,還是每個函數都共享相同的閉包? (即像這樣)

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

回答

4

從我可以找到(從不完全是簡單的源進行粗略的閱讀和一些實驗來看),它會在每次複製克隆的對象。這個函數可能沒有必要將它作爲const &的參數,但通常該對象可能會被該函數改變。如果該對象的拷貝成本很高,通過引用捕獲它是否有意義(想起boost::refboost::cref),或者如果原始對象在調用點不存在,則捕獲boost::shared_ptr並編寫適配器方法,它解開智能指針並調用someFunction

編輯:從實驗開始,只要複製boost::function,它不僅將複製該對象,而且它還將在boost::bind內複製多次。我測試使用利用升壓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 

因此,調用copy構造函數創建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 

所以五人副本只是對象綁定。因此,我總是會避免捕獲任何在代碼的任何遠程性能敏感部分中都至少降低每個值的複製成本的任何內容。相比之下GCCS std::tr1::bindstd::bind執行好得多(結合的std :: TR1 ::功能/性病::功能)(代碼基本上相同於第一testcode,只是代替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 inliner以內聯一些並消除多餘的拷貝構造函數tr1::bind仍然得到了更好的優化,然後boost::bind,但仍然遠未達到最優。

當然,一直使用不同的編譯標誌/編譯器進行這樣的測試YMMV

+0

叫我寫了一個測試程序,它看起來像存儲對象的拷貝構造函數每次拷貝函數對象時不會被調用。另外,boost :: bind調用拷貝構造函數11次! – Chris 2011-12-31 15:03:57

+0

@克里斯:好吧,所以我的測試不是僥倖得知。所以,如果一個人可以使用C++ 11,看起來std :: bind是一種遠遠不夠的方式(儘管個人而言我只是使用lambdas代替)。 – Grizzly 2011-12-31 15:44:10

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. no constructor當通過ff()ff2()
  4. 調用的一個叫g(const a &)當物體散發出來的範圍
相關問題