2015-05-24 13 views
6

mam połączyć prostą C++ klasy Timer, która ma wywołać określoną funkcję okresowo z różnych przykładów na SO następująco:C++ 11: Wywoływanie C funkcja ++ okresowo

#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; 
}; 

teraz chcę zadzwonić to z C++ klasa jako followsL

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

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

jednak wymaga to z błędem

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

Czy działa z wolnostojącą funkcją 'void foo() {}'? – stefan

+1

Czy rzeczywiście czekasz na zakończenie wątku? Całkowicie go odłączyłeś, więc czy jesteś pewien, że główny wątek po prostu nie zniszczył już odpowiedniego obiektu 'Procesor'? – KillianDS

+0

@KillianDS najprawdopodobniej to, co się dzieje. Luca, powinieneś opublikować [MCVE] (http://stackoverflow.com/help/mcve). Co się stanie, jeśli '.join()' wątek? – vsoftco

Odpowiedz

16

PROBL W twoim kodzie jest to, że twoje wyrażenie lambda wewnątrz twojej funkcji "start" przechwytuje zmienne lokalne przez odniesienie, używając składni [&]. Oznacza to, że lambda przechwytuje zmienne interval i func przez odniesienie, które są zarówno zmiennymi lokalnymi do funkcji start(), a zatem znikają po powrocie z tej funkcji. Ale po powrocie z tej funkcji lambda wciąż żyje wewnątrz oddzielonej nici. Wtedy pojawia się wyjątek "zła funkcja wywołania", ponieważ próbuje wywołać func przez odniesienie do obiektu, który już nie istnieje.

Co trzeba zrobić, to uchwycić zmiennych lokalnych według wartości, ze składnią [=] na lambda, a więc:

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

To działa, gdy próbuję go.

Albo, można również wymienić się wartości, które chcesz uchwycić bardziej wyraźny (które generalnie zalecane dla 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(); 
} 

EDIT

Jak inni zwrócili uwagę, korzystanie oderwanego wątku nie jest doskonałym rozwiązaniem, ponieważ łatwo można zapomnieć zatrzymać wątek i nie można sprawdzić, czy już działa. Powinieneś także zrobić atomową flagę _execute, aby upewnić się, że nie zostanie ona zoptymalizowana, a odczyty/zapisy są wątkowane. Zamiast tego można to zrobić:

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

Dzięki za wspaniałą odpowiedź. Czy bezpieczne jest użycie metody detach(), jak w twoim przykładzie. Niektóre z komentarzy wspomnianych przy użyciu join() – Luca

+1

@ Lucca zgodziłbym się z innymi, że nie jest wspaniale stworzyć pozbawiony wątek jak ty. Powinieneś uczynić obiekt wątku elementem danych klasy 'CallBackTimer', a następnie wykonać' _execute = false; ', a następnie' thd.join() 'w destruktorze klasy' CallBackTimer'. Ponadto, twoja flaga '_execute' powinna być' volatile' lub 'std :: atomic ', aby upewnić się, że twoja pętla while nie zostanie zoptymalizowana do pętli 'while (true)'. Zrobię edycję, żeby to pokazać. –

+0

Dziękuję bardzo za życzliwość i pomoc! – Luca