I have this simple function:
class Timer {
std::atomic<bool> active{true};
public:
void setInterval(auto function, int interval);
void stop();
};
void Timer::setInterval(auto function, int interval) {
active = true;
std::thread t([=]() {
while(active.load()) {
std::this_thread::sleep_for(std::chrono::milliseconds(interval));
if(!active.load()) return;
function();
}
});
t.detach();
}
void Timer::stop() {
active = false;
}
Now when I try to run it:
int main()
{
printf("start\n");
Timer* timer = new Timer();
timer->setInterval([&]() {
std::cout << "hello" << std::endl;
},1000);
return 0;
};
Only the start printed from the main class, it never reaches the function inside the setInterval function. It just stops the app with no error.
The compile/link command:
/usr/bin/g++ -fdiagnostics-color=always -std=c++14 -pthread -g /home/vagrant/cpp/batch/main.cpp -o /home/vagrant/cpp/batch/main
Here is one of the many ways of correctlty using a thread object to run your timer.
#include <atomic>
#include <thread>
#include <chrono>
#include <iostream>
class Timer {
private:
std::atomic<bool> active = false;
std::unique_ptr<std::thread> timer_thread;
public:
// this class needs a destructor.
~Timer()
{
stop();
}
// this function name is misleading,
// void setInterval(auto function, int interval);
//
// let's rename it to what it does, also try to keep
// the function argument last, this makes the code
// more legible when calling with a lambda.
//
// you do not know if 'cb', the caller's callable object
// is copyable or moveable, so you should use perfect
// forwarding, that's only (readily) available from a
// template.
template <typename Fn>
void start(int interval, Fn&& cb)
{
stop(); // make sure we're stopped.
active = true;
// start the thread, the forward<> of the callback object is
// important!
timer_thread = std::make_unique<std::thread>(
[this, ms = std::chrono::milliseconds(interval), cb = std::forward<Fn>(cb)]() {
while (active) // calls atomic<>::load() automatically
{
std::this_thread::sleep_for(ms);
if (active)
cb();
}
});
}
// this is where we kill the thread, and wait for it to exit.
void stop()
{
active = false;
if (timer_thread)
{
timer_thread->join();
timer_thread.reset();
}
}
bool isRunning() const
{
return active;
}
};
int main()
{
std::cout << "start\n";
Timer timer; // There's absolutely no need to place timer on the heap.
int n = 4; // simple countdown to 1 for example.
timer.start(1000, [&]() {
std::cout << "countdown: " << n << '\n';
if (--n <= 0)
timer.stop();
});
// We should wait for the timer to stop, before exiting the program.
while (timer.isRunning()) {}
return 0;
};