標籤:

拷貝VS函數

這次要吐的槽其實一點都不新鮮。不過最近沒什麼能寫的,就搬上來濫竽充數啦~

(才不是我偷懶疏於學習呢!)( ̄▽ ̄* )ゞ

C++11引入的std::function大大方便了回調函數的寫法——不需要再寫醜陋的void* userdata啦!配合起std::bind和lambda效果更佳。(≧?≦)?

不過lambda隱藏的坑也很多。比如this的捕獲是引用,容易產生生命周期問題。C++17引入了*this的傳值捕獲來部分解決了這個問題。

但歸根究底,沒有gc實在是不方便處理這堆爛攤子,而shared_ptr原本是能部分達成gc的功能的,但本身卻是個庫層面的非侵入式的引用計數,因此面對this也是無計可施。

╮(╯-╰)╭

今天要說的是function的坑。

在線程池或者非同步操作里,把任務包裝成一個function是一種很直觀的做法。而為了實現同步,用promise+future也是很常見且很方便的手段。

然而,兩者就像柿子和螃蟹一樣,「水火不相容」!(傳播謠言罪預定)

(^^ゞ

隨手寫一段demo。由於promise是uncopyable的,所以用了move去捕獲。

std::promise<void> pms;std::function<void(void)> func = [pms = std::move(pms)]{ pms.set_value(); };

顯然,上面的代碼是不能跑的~(明明在這個問題上卡了十分鐘……)

因為promise的set_value是非const的,也就是lambda內部修改了自身的狀態。而默認的lambda是被看作重載了operator() const,自然會出現衝突。

ㄟ( ▔, ▔ )ㄏ

這個問題連intellisense都能發現,解決方法也很簡單,加上mutable。

std::promise<void> pms;std::function<void(void)> func = [pms = std::move(pms)]() mutable { pms.set_value(); };

現在intellisense滿意了,不報錯了。但是執行編譯就報錯了。

C2280「<lambda>::<lambda>(const <lambda> &)」: 嘗試引用已刪除的函數

(°ー°〃)

錯誤提示很直觀,lambda的拷貝構造函數的問題。由於捕獲的promise不能copy,lambda這個「匿名類」自然也不能copy。那麼問題來了,哪裡發生了copy呢?

作為一個左值右值學習不到位的三流C++代碼寫手,面對這一情況的第一反應就是,「=」這個賦值初始化觸發了拷貝!( ̄︶ ̄)↗ 

然鵝改成直接初始化也是一模一樣的錯誤。

std::function<void(void)> func([pms = std::move(pms)]() mutable { pms.set_value(); });


其實原因很簡單。

std::function 滿足可複製構造 (CopyConstructible) 和可複製賦值 (CopyAssignable)

std::function - cppreference.com

這個問題知乎上也有人回答過

Lambda捕獲了被禁止移動的對象,但Move 「Lambda對象」時,卻顯式調用其的拷貝構造??

www.zhihu.com圖標

至於為什麼非要可複製,這就無從得知了。

由於function自身已經相當於一定程度的type erasure,因此同樣type的function可能有的可複製,有的卻不能複製。

而把function設定為只能移動呢,又不合適。

┌( ′_ゝ` )┐


幸好,解決方法也很簡單,把promise拿shared_ptr包一下就行啦~

不過C++自身的問題也算是暴露無疑:想要zero overhead,又想要包容一切?那就在複雜的迷宮裡屢屢撞壁吧~

┗|`O′|┛


推薦閱讀:

C/C++ 里指針聲明為什麼通常不寫成 int* ptr 而通常寫成 int *ptr ?
int 在VS debug win32和debugx64下的區別?
如何學好編程語言以及進行軟體開發?
如何理解c++中的引用摺疊?
國內/外的編程圈子有什麼不一樣?

TAG:編程 | CC |