Wednesday 15 May 2013

std::chrono

C++11 has introduced the <chrono> header and, in turn, the std::chrono sub-namespace. This is good.

I don't think many in the standards committee would claim this is a revolutionary addition; there are only a couple of classes that can be instantiated and a couple of free functions allowing us to cast between some of the representations of time. Nonetheless, cross-platform features for reasoning about time are extremely welcome and could go a long way to improving code in this area.

One thing I can always get on board with is improving the semantic richness and the strength of the types that fly around an application. You've probably seen code for dealing with seconds and minutes that works something like this:
 const int MINUTES_TO_SECONDS = 60;  
   
 const int timeToSurviveMinutes = 10;  
   
 const int timeToSurviveSeconds = timeToSurviveMinutes * MINUTES_TO_SECONDS;  

I don't think anybody loves code like this, but we accept it. Putting aside the overflow issues, the compiler has no comprehension of what your intent for these variables is, so you can merrily (accidentally) interchange them:
 void secondsToParsecs(int seconds)  
 {  
     // PARSECS ARE A UNIT OF DISTANCE NOT TIME  
 }  
   
 void doStuff()  
 {  
     const int MINUTES_TO_SECONDS = 60;   
     
     const int timeToSurviveMinutes = 10;   
     
     const int timeToSurviveSeconds = timeToSurviveMinutes * MINUTES_TO_SECONDS;   
   
     // bug! This function expects a value in seconds  
     secondsToParsecs(timeToSurviveMinutes);  
 }  
   

So, now we have the tools to represent these properly! This is C++, so they're extremely generic and template-heavy, but at least you can do things like this:
 #include <chrono>  
   
 void secondsToParsecs(const std::chrono::seconds &s)  
 {  
     // ...  
 }  
   
 void doStuff()  
 {  
     // represents 15 minutes  
     const std::chrono::minutes m{15};  
    
     // convert to seconds
     const std::chrono::seconds s{std::chrono::duration_cast<std::chrono::seconds>(m)};  
   
     // bug! This time, the compiler saves us:  
     secondsToParsecs(m);  
 }  

So far, we've looked at one of the three main features in the <chrono> header: durations. Durations are part of the <chrono> trifecta, which also includes time points and clocks. The three are closely related:
  • Time points are durations relative to an epoch
  • Clocks relate time points to real-world time
So we can create simple time points using durations relative to the system epoch:
 #include <chrono>  
 #include <ctime>  
 #include <iostream>  
 int main(int argc, char **argv)  
 {  
      // get a time point zero seconds after the epoch  
      std::chrono::seconds noTime{0};  
      std::chrono::system_clock::time_point systemEpoch{noTime};  

      // convert to a time_t for convenient printing  
      std::time_t tt{std::chrono::system_clock::to_time_t(systemEpoch)};  
      std::cout << "System epoch is: " << ctime(&tt) << std::endl;  
      return 0;  
 }  

Note that I've used std::chrono::system_clock::time_point as a shortcut for std::chrono::time_point<std::chrono::system_clock>. The former is a simple typedef for the latter.

Finally, we can use clocks to reason about real-world time in terms of time points. The standard library provides high_resolution_clock, steady_clock and system_clock:
  • system_clock is a realtime clock
  • steady_clock is a clock that never returns a value lower than a previous return; it is monotonic. This makes it useful for calculating time intervals (timing operations)
  • high_resolution_clock is a clock with the highest resolution. The standard states that it might be a synonym for either of the aforementioned clocks and the version of the standard library I've been using, it's a typedef for system_clock.
We combine these features to do interesting things relative to the current time. We can access the current time and use a duration to create a time point exactly ten hours from now:
 #include <chrono>  
 #include <ctime>  
 #include <iostream>  
 int main(int argc, char **argv)  
 {  
      // get the current time:  
      std::chrono::system_clock::time_point now{std::chrono::system_clock::now()};  

      // calculate ten hours later:  
      std::chrono::system_clock::time_point tenHoursLater{now + std::chrono::hours(10)};  

      // what have we got?  
      std::time_t tt{std::chrono::system_clock::to_time_t(tenHoursLater)};   
      std::cout << "Ten hours from now is: " << ctime(&tt) << std::endl;   
      return 0;  
 }  

Better still, we can calculate the time a certain operation takes without resorting to platform-specific APIs:
 #include <chrono>  
 #include <ctime>  
 #include <iostream>  
   
 int main(int argc, char** argv)  
 {  
      // get the current time:  
      std::chrono::steady_clock::time_point tp1{std::chrono::steady_clock::now()};  
   
      // perform some long and boring operation  
      doLongAndBoringOperation();  
   
      // get the current time again:  
      std::chrono::steady_clock::time_point tp2{std::chrono::steady_clock::now()};  
   
      // see how long it took  
      std::chrono::duration<double> timeTaken{std::chrono::duration_cast<std::chrono::duration<double>>(tp2-tp1)};  
      std::cout << "Wow, that took " << timeTaken.count() << " seconds!" << std::endl;  
   
      return 0;  
 }  

Not bad, eh?