2015-05-24 110 views
6

我已經把一個簡單的C++是應該Timer類週期性地從各種例子對SO如下調用給定函數:C++ 11:調用C++函數定期

#include <functional> 
#include <chrono> 
#include <future> 
#include <cstdio> 

class CallBackTimer 
{ 
public: 
    CallBackTimer() 
    :_execute(false) 
    {} 

    void start(int interval, std::function<void(void)> func) 
    { 
     _execute = true; 
     std::thread([&]() 
     { 
      while (_execute) { 
       func();     
       std::this_thread::sleep_for(
       std::chrono::milliseconds(interval)); 
      } 
     }).detach(); 
    } 

    void stop() 
    { 
     _execute = false; 
    } 

private: 
    bool   _execute; 
}; 

現在我希望讓此從一個C++類如followsL

class Processor() 
{ 
    void init() 
    { 
     timer.start(25, std::bind(&Processor::process, this)); 
    } 

    void process() 
    { 
     std::cout << "Called" << std::endl; 
    } 
}; 

然而,這要求與該錯誤

terminate called after throwing an instance of 'std::bad_function_call' 
what(): bad_function_call 
+0

它是否與一個獨立函數'void foo(){}'一起使用? – stefan

+1

你是否真的等待線程在某個點完成?你完全分離它,你確定你的主線程還沒有銷燬相關的'Processor'對象嗎? – KillianDS

+0

@KillianDS這很可能是發生了什麼事情。 Luca,你應該發佈[MCVE](http://stackoverflow.com/help/mcve)。如果你'.join()'線程會發生什麼? – vsoftco

回答

16

的PROBL在你的代碼中,你的「start」函數中的lambda表達式通過引用捕獲局部變量,使用[&]語法。這意味着lambda通過引用捕獲intervalfunc變量,它們都是start()函數的局部變量,因此它們在從該函數返回後消失。但是,從該函數返回後,lambda在分離的線程內仍然活着。那是因爲你試圖通過引用不存在的對象來調用func而得到「壞功能調用」異常。

你需要做的是通過值捕獲的局部變量,對拉姆達的[=]語法,所以:

void start(int interval, std::function<void(void)> func) 
{ 
    _execute = true; 
    std::thread([=]() 
    { 
     while (_execute) { 
      func();     
      std::this_thread::sleep_for(
      std::chrono::milliseconds(interval)); 
     } 
    }).detach(); 
} 

這工作時,我嘗試它。

或者,你也可以列出你想更明確(我一般推薦lambda表達式)捕獲值:

void start(int interval, std::function<void(void)> func) 
{ 
    _execute = true; 
    std::thread([this, interval, func]() 
    { 
     while (_execute) { 
      func();     
      std::this_thread::sleep_for(
      std::chrono::milliseconds(interval)); 
     } 
    }).detach(); 
} 

編輯

正如其他人所指出的那樣,使用的分離線程並不是一個很好的解決方案,因爲您可能很容易忘記停止線程,並且無法檢查它是否已經在運行。此外,你應該使_execute標誌爲原子,只是爲了確保它不會被優化,並且讀/寫是線程安全的。你可以這樣做:

class CallBackTimer 
{ 
public: 
    CallBackTimer() 
    :_execute(false) 
    {} 

    ~CallBackTimer() { 
     if(_execute.load(std::memory_order_acquire)) { 
      stop(); 
     }; 
    } 

    void stop() 
    { 
     _execute.store(false, std::memory_order_release); 
     if(_thd.joinable()) 
      _thd.join(); 
    } 

    void start(int interval, std::function<void(void)> func) 
    { 
     if(_execute.load(std::memory_order_acquire)) { 
      stop(); 
     }; 
     _execute.store(true, std::memory_order_release); 
     _thd = std::thread([this, interval, func]() 
     { 
      while (_execute.load(std::memory_order_acquire)) { 
       func();     
       std::this_thread::sleep_for(
       std::chrono::milliseconds(interval)); 
      } 
     }); 
    } 

    bool is_running() const noexcept { 
     return (_execute.load(std::memory_order_acquire) && 
       _thd.joinable()); 
    } 

private: 
    std::atomic<bool> _execute; 
    std::thread _thd; 
}; 
+0

謝謝你的回答。使用detach()就像你的例子一樣安全。一些使用join()提到的評論 – Luca

+1

@Luca我會同意其他人的看法,創建一個像你一樣的分離線程並不好。你應該做的是使線程對象成爲CallBackTimer類的數據成員,然後在你的CallBackTimer類的析構函數中執行'_execute = false;'後跟'thd.join()'。另外,你的'_execute'標誌應該是'volatile'或'std :: atomic ',以確保你的while循環不會被優化爲'while(true)'循環。我會做一個編輯來證明這一點。 –

+0

非常感謝您的善意和幫助! – Luca