標籤:

C++11 function 實現委託機製為什麼會自動釋放委託的函數?

代碼示例

cplus11_feature_E/test_delegate_pro.h at master · lion117/cplus11_feature_E · GitHub

問題描述:

1. 將Boss的類方法委託給Worker之後,一旦作用域釋放,委託給Worker的類方法竟然被釋放了,變成空了?

2. Boss僱傭了Worker1, Worker2, Worker2運行後, Worker1的名字變成Worker2 .

問題關閉.在各位大神的講解下,問題順利解決.

1. 原錯誤用法繼續保留.作為反面教材

2. 非常感謝各位大神的指點.有種醍醐灌頂的感覺.對自身錯誤用法,類構建析構和lambda表達式有進一步的認知

根據各位大神的指點,用std:bind 和lambda分別實現了一遍委託功能.

lambda對類this的閉包處理方式實在是太棒了

lambda方案實現:

cplus11_feature_E/test_delegate_pro.h at dev-lambda · lion117/cplus11_feature_E · GitHub

std::bind方案實現:

cplus11_feature_E/test_delegate_pro.h at dev-bind · lion117/cplus11_feature_E · GitHub


超級慘的車禍現場...

第零點

auto i_process_task = std::bind(std::move(Boss::onProcess), std::move(*this), _1, _2);

這行過後,this 所指向的 i_boss 對應的對象會被移動到了 i_process_task 里的某個變數里,i_boss._work_list 只剩個空 vector,因此循環第一遍結束後回到

for (auto itor : _worker_list)

這裡迭代器已失效。VS Debug 配置下會被

_DEBUG_ERROR("vector iterator not incrementable");

搞掛掉。

解決方案是,把 std::move(*this) 換成 this。

第一點

for (auto itor : _worker_list)

這麼寫等於創建了個臨時變數 itor,每次循環把 _worker_list 里的對象複製到 itor。結果導致每次循環都把 i_process_task 和 i_result_task 賦值給了一個臨時對象。第二次循環里這個臨時變數里的對象會被新的 worker2 對象替代,循環結束後這個對象及其的成員變數就析構了。你新建的線程里繼續訪問 lambda 綁定的這個臨時變數有可能訪問到的是 worker2 或者被析構後的無效空間。。解決方案是改成 auto itor。

第二點

worker1 里新線程綁定的 this 指針指向的是循環創建的臨時變數 itor。第一遍循環結束後就沒舊對象的事兒了,第二遍循環在相同的位置創建 worker2,導致 worker1 創建的線程里通過指針訪問到了新的 worker2 對象,看起來像是 worker1 改名了。

建議閱讀 c++ - Using std::bind with member function, use object pointer or not for this argument? 和 Effective Modern C++ Item 23。

最後,可以考慮給 Worker 和 Boss 都加上下面這幾個函數,然後運行或者單步,體會一下到底有多慘

Boss() {
cout &<&< "Boos::Boos();" &<&< endl; } Boss(const Boss o) :_worker_list(o._worker_list) { cout &<&< "Boos::Boos(const Boos);" &<&< endl; } Boss(Boss o) :_worker_list(std::move(o._worker_list)) { cout &<&< "Boos::Boos(Boos);" &<&< endl; } ~Boss() { cout &<&< "Boos::~Boos();" &<&< endl; }

部分輸出:

# main
00D759BE Boos()
# hireWorker
008FFC78 Worker(string) tom report
008FFC00 Worker(string) leo report
00DC1A18 Worker(const Worker)
00DC42E8 Worker(Worker)
00DC1A18 ~Worker()
00DC4358 Worker(const Worker)
008FFC00 ~Worker()
008FFC78 ~Worker()
# loop
008FFC38 Worker(const Worker)
-- 1 1 --
00D752B2 Boos(Boos)
-- 1 2 --
00DC43F8 Worker(const Worker)
00DC4468 Worker(const Worker)
00D75242 Boos(const Boos)
00D74ABA Boos(Boos)
00D752AE ~Boos()
00DC4D10 Worker(const Worker)
00DC4D80 Worker(const Worker)
00D7488E Boos(const Boos)
accept task
00D7498A ~Boos()
00DC43F8 ~Worker()
00DC4468 ~Worker()
-- 1 3 --
00D752B2 Boos(Boos)
-- 1 4 --
00D75242 Boos(const Boos)
00D74ABA Boos(Boos)
00D752AE ~Boos()
00D7488E Boos(const Boos)
00D7498A ~Boos()
-- 1 5 --
report to boss: 0


問題如樓下匿名用戶所講,我來提供修改意見

首先range-based-for的參數沒有用引用,所以你使用的並不是vector里的worker,而是vector里worker的拷貝

for(auto worker : _worker_list

然後,既然都已經用了C++11的東西,那為什麼還要用std::bind呢,lambda不好嗎,用了lambda,也不用擔心什麼bind出的東西出作用域之後被銷毀的問題

auto i_process_task = [](const string str, int i) { onProcess(str, i);};
auto i_result_task = [](const string str, const string i) { onComplete(str, i);};

還有我發現你不愛使用引用,把參數寫成const 是個好習慣,可以避免很多拷貝的麻煩事


我注意到有三個問題:

1. range for 遍歷_worker_list的時候沒有用引用, 導致臨時生成Worker

2. bind的時候第一個參數給的是std::move(*this), 這個意味著i_process_task保存的Boss實例不是main裡面那個,可以列印對應的this指針驗證

3. i_process_task和i_result_task分配在棧上, 所以離開作用域會被析構, 並引起析構生成的Boss實例, 可以在Boss的析構函數列印信息驗證

換名的現象應該是1造成的, 因為i_thread裡面捕獲了worker的this指針來訪問_worker_name, 當第二次循環的時候, 臨時生成的Worker的地址和第一次一樣,所以都打出來的是第二個worker的名字

至於std::move(*this)那個地方, 我分析半天沒理清, 試著delete Boss的copy constructor等, gcc報的錯糊我一臉, 所以放棄了, 等大神分析吧

另外, 把std::move(*this)換成this, 能正確工作


推薦閱讀:

如何格式化代碼能夠將類成員/函數的名字對齊?
做遊戲與搞圖形學有什麼聯繫?
微軟會把 clang 擴展到可以徹底替換 C1,並真的換掉 C1 嗎?
如何評價 C++14 ?
如何解決光線跟蹤中浮點數誤差導致的渲染錯誤?

TAG:編程 | C |