拷貝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對象」時,卻顯式調用其的拷貝構造?至於為什麼非要可複製,這就無從得知了。
由於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++中的引用摺疊?
※國內/外的編程圈子有什麼不一樣?