2015-05-24 6 views
6

я соединил простой C++ класса таймера, который должен вызывать данную функцию периодически из различных примеров на 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

Действительно ли вы дожидаетесь окончания нитки в какой-то момент? Вы полностью отделите его, так что вы уверены, что ваш основной поток просто не уничтожил соответствующий объект «Процессор»? – KillianDS

+0

@KillianDS это, скорее всего, что происходит. Лука, вы должны отправить сообщение [MCVE] (http://stackoverflow.com/help/mcve). Что произойдет, если вы '.join()' поток? – vsoftco

ответ

16

пробл em в вашем коде является то, что ваше выражение лямбда внутри вашей «стартовой» функции захватывает локальные переменные по ссылке, используя синтаксис [&]. Это означает, что лямбда фиксирует переменные interval и func по ссылке, которые являются как локальными переменными, так и функцией start(), и, таким образом, они исчезают после возвращения из этой функции. Но, вернувшись с этой функции, лямбда все еще жива внутри отдельной нити. Именно тогда вы получаете исключение «bad-function-call», потому что оно пытается вызвать 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(); 
} 

Это работает, когда я пытаюсь его.

Или вы могли бы также перечислить значения, которые вы хотите, чтобы захватить более явно (я обычно рекомендую для лямбды):

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(); 
} 

EDIT

Как уже отмечалось, использование отделяемой нити не является отличным решением, потому что вы можете легко забыть остановить поток, и у вас нет возможности проверить, запущен ли он. Кроме того, вы должны, вероятно, сделать флаг _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, а затем выполнить '_execute = false; ', за которым следует' thd.join() 'в деструкторе вашего класса CallBackTimer. Кроме того, ваш флаг '_execute' должен быть либо' volatile', либо 'std :: atomic ', чтобы убедиться, что цикл while не будет оптимизирован для цикла while (true). Я сделаю редактирование, чтобы показать это. –

+0

Большое спасибо за вашу доброту и помощь! – Luca

Смежные вопросы