A Simple Timer in C++

Some languages, such as JavaScript or Visual Basic, offer the feature of a timer, that is to say an object that calls some code at defined intervals. At the time of this writing (C++17) and to my knowledge, C++ doesn’t offer such a feature.

A library called timercpp , that replicates in C++ this handy functionality of other languages, was on the front page of HN. It has a nice interface that allows for expressive code, however I don’t think it is quite ready for being used in production.

If it is not production-ready, why talk about it then? For two reasons:

its implementation is instructive to learn about C++ standard library’s basic usages of threads,

the reasons why it should maybe not be used in production are also instructive.

I learned several things when looking at this library and the discussion around it, so I figured maybe other people could find this instructive too.

timercpp uses an interface inspired from JavaScript, by implementing a setTimeout and a setInterval functions. This leads to a nice interface:

Timer t = Timer(); t.setInterval([&]() { cout << "Hey.. After each 1s..." << endl; }, 1000); t.setTimeout([&]() { cout << "Hey.. After 5.2s. But I will stop the timer!" << endl; t.stop(); }, 5200); 1 2 3 4 5 6 7 8 9 10 Timer t = Timer ( ) ; t . setInterval ( [ & ] ( ) { cout << "Hey.. After each 1s..." << endl ; } , 1000 ) ; t . setTimeout ( [ & ] ( ) { cout << "Hey.. After 5.2s. But I will stop the timer!" << endl ; t . stop ( ) ; } , 5200 ) ;

setInterval allows to run the code of the same function repeatedly, at a given interval. In the above example, the function is a lambda that displays “Hey.. After each 1s…”. And setTimeout plans one execution of a function in a given amount of time, here printing “Hey.. After 5.2s. But I will stop the timer!” and stopping the timer, in 5200 milliseconds.

Let’s see how this interface is implemented. On top of seeing what’s behind that nice facade, this will let us get more familiar with the std::thread interface by studying a simple example of its usage, and will also show us the drawbacks of the library.

The interface of Timer

The interface of the Timer object is this:

class Timer { bool clear = false; public: void setTimeout(auto function, int delay); void setInterval(auto function, int interval); void stop(); }; 1 2 3 4 5 6 7 8 class Timer { bool clear = false ; public : void setTimeout ( auto function , int delay ) ; void setInterval ( auto function , int interval ) ; void stop ( ) ; } ;

This looks more like a C++20 interface, with auto as a type in the interface. To make it compliant with C++17, we could adjust it with templates:

class Timer { bool clear = false; public: template<typename Function> void setTimeout(Function function, int delay); template<typename Function> void setInterval(Function function, int interval); void stop(); }; 1 2 3 4 5 6 7 8 9 10 11 12 class Timer { bool clear = false ; public : template < typename Function > void setTimeout ( Function function , int delay ) ; template < typename Function > void setInterval ( Function function , int interval ) ; void stop ( ) ; } ;

Even though the templates don’t add any information here. The code was more concise without them, which is a hopeful sign for C++20.

Implementation of setTimeout

Here is the implementation of setTimeout . We will go through it line by line afterwards:

void Timer::setTimeout(auto function, int delay) { this->clear = false; std::thread t([=]() { if(this->clear) return; std::this_thread::sleep_for(std::chrono::milliseconds(delay)); if(this->clear) return; function(); }); t.detach(); } 1 2 3 4 5 6 7 8 9 10 void Timer :: setTimeout ( auto function , int delay ) { this -> clear = false ; std :: thread t ( [ = ] ( ) { if ( this -> clear ) return ; std :: this_thread :: sleep_for ( std :: chrono :: milliseconds ( delay ) ) ; if ( this -> clear ) return ; function ( ) ; } ) ; t . detach ( ) ; }

The first line sets the flag that controls if the timer is active or inactive, to set it as active:

this->clear = false; 1 this -> clear = false ;

Perhaps calling the variable active instead of clear would have allowed to have a positive name and made the code easier to read.

Next up we instantiate a thread object, by using its constructor that accepts a function:

std::thread t([=]() { 1 std :: thread t ( [ = ] ( ) {

That (lambda) function starts by checking if the timer is still active (otherwise it return s immediately) as it could have been stopped by another function as we will see later. If it is active, it waits for the indicated delay :

if(this->clear) return; std::this_thread::sleep_for(std::chrono::milliseconds(delay)); 1 2 if ( this -> clear ) return ; std :: this_thread :: sleep_for ( std :: chrono :: milliseconds ( delay ) ) ;

The sleep_for function makes the thread it is invoked on (here, the one associated with the std::thread we’re building) wait for at least the indicated delay. In practice it could be a little longer if the OS is not ready to hand back the execution to the thread.

Then we check again if the timer is still active, and if it is we invoke the function passed to setTimeout :

if(this->clear) return; function(); 1 2 if ( this -> clear ) return ; function ( ) ;

Then we finish executing the constructor of the std::thread :

}); 1 } ) ;

To understand what’s happening here, we need to realize that there are two things we call “threads” here:

the real thread that is controlled by the OS,

the thread object, of type std::thread , in our program.

At the end of the construction of the thread object, the real thread starts executing the code of the above lambda (or at least as soon as the OS allows it).

But this thread object has a very short life: it will be destroyed at the end of the setTimeout function. And we would like the real thread to outlive the thread object. To to this, we detach one from the other:

t.detach(); 1 t . detach ( ) ;

The real thread can then live on its own life even after the thread object is destroyed at the end of setTimeout function:

} 1 }

Implementation of setInterval

If the implementation of setTimeout is clear for you, the one of setInterval shouldn’t be a problem. Even better, a good exercise would be to try to code it up yourself.

I’m always curious to know about how many people do take the time to pause, set the blog post aside, and code up the example. If you do this, you will learn more than by a simple reading. To make it easier, here is an online compiler webpage with all the code already written except the implementation of setInterval .

Once you’ve tried it (or if you don’t), here is the implementation in the library:

void Timer::setInterval(auto function, int interval) { this->clear = false; std::thread t([=]() { while(true) { if(this->clear) return; std::this_thread::sleep_for(std::chrono::milliseconds(interval)); if(this->clear) return; function(); } }); t.detach(); } 1 2 3 4 5 6 7 8 9 10 11 12 void Timer :: setInterval ( auto function , int interval ) { this -> clear = false ; std :: thread t ( [ = ] ( ) { while ( true ) { if ( this -> clear ) return ; std :: this_thread :: sleep_for ( std :: chrono :: milliseconds ( interval ) ) ; if ( this -> clear ) return ; function ( ) ; } } ) ; t . detach ( ) ; }

This is the same technology as the one used for setTimeout : we create a thread object that starts by being linked to a real tread, then we .detach it so that they have their separate lives (even if the one of the thread object is about to end smashed against a closing brace).

The lambda function of the thread repeatedly checks if the timer is still active, waits for the interval time and executes the function.

Finally, to stop the timer, the stop method sets the clear flag:

void Timer::stop() { this->clear = true; } 1 2 3 void Timer :: stop ( ) { this -> clear = true ; }

The drawbacks of the library

Why shouldn’t we use this library in production? What do you think?

One issue is the very fact that it uses threads. Indeed, the JavaScript equivalent uses an event loop, and does not create a new thread for each invocation of setTimeout or setInterval .

Also, the clear flag is read and written from several threads, and – correct me if I’m wrong – there is nothing to protect it from a race condition.

Another library that allows to use timers is C++ is Boost Asio, and it does use an event loop. But it’s a much, much larger library, planned to be integrated in standard C++. But that’s a topic for another post.

You will also like

Share this post! Don't want to miss out ?