1. 程式人生 >實用技巧 >其它 >boost:chrono

boost:chrono


  • 跨平臺、可移植一直是很多C++程式設計師追求的目標。但由於C++是一種中級語言,很多時候都要和作業系統打交道,因而功能程式碼常常和平臺結合很緊密,難以做到真正的跨平臺,再向其他平臺移植程式碼的時候修改作業系統相關的程式碼通常是必不可少的工作
  • boost庫提供了數個作業系統相關的庫,部分的遮蔽了作業系統的底層細節,能夠提高程式的可移植性。比如說system庫,它封裝了作業系統底層的錯誤程式碼,為上層提供了一個可移植的統一的處理介面; chrono、cpu_time庫基於作業系統的API提供高精度的時間功能

概述

chrono完全實現了C++標準裡定義的時間處理庫,並且額外增加了一些功能。

  • chrono庫裡的很多時間概念於date_time庫類似,但它更側重於表達”計算機世界“裡的時間,所以是一個很重要的基礎庫,被cpu_time、thread等庫使用
  • chrono庫需要編譯,並且依賴system庫,在jamfile裡指定lib的語句是:lib boost_chrone: boost_system:<name>boost_chrono;
  • chrono庫提供巨集BOOST_CHRONO_HEADER_ONLY,可以把chrone庫完全標頭檔案化,不需要編譯或者連結(但還需要連結system庫)
  • chrono庫位於名字空間boost::chrono,需要包含標頭檔案<boost/chrono.hpp>,為了使用擴充套件功能( chrono庫另有一個頭檔案<boost/chrono/include.hpp>,包含了很多擴充套件功能),需要定義巨集BOOST_CHRONO_EXTENSIONS, 即:
//#define BOOST_ERROR_CODE_HEADER_ONLY   // 無需編譯即可使用system庫
#define BOOST_CHRONO_HEADER_ONLY         // 無需編譯即可使用chrono庫
#define BOOST_CHRONO_EXTENSIONS			 // 使用擴充套件功能
#include <boost/chrono.hpp>				 // 標頭檔案
using namespace boost;
using namespace boost::chrono;

時間長度

chrono庫定義了時間長度的表示duration(注:chrono不關心歷史日期,沒有date_time庫裡的date的概念),在概念上於date_time庫的time_duration相同,都表示一定長度的時間,但duration側重於編譯期的時間單位表示,更接近ratio,所以與time_duration的介面非常不同。

類摘要

  • duration有兩個模板引數,第一個引數Rep是一個算數型別(或者可以當作算術型別運算)比如int、double,表示時間單位的數量;第二個引數Period表示時間單位,以秒為基準,它必須是ratio型別,而且Period::num必須是正數
template <class Rep, class Period>  
class duration
{
    public:
        typedef Rep rep;                         //內部型別定義
        typedef Period period;
    private:
        rep rep_;                                //時間單位的計數
    public:
        BOOST_CONSTEXPR duration();              // 建構函式
        explicit duration(const Rep2& r);        
        //~duration() {} //= default;
//        BOOST_CONSTEXPR        duration(const duration& rhs) : rep_(rhs.rep_) {} // = default;
        duration& operator=(const duration& rhs);

        // conversions
        duration(const duration<Rep2, Period2>& d );
       
        BOOST_CONSTEXPR rep count() const ;

        // arithmetic

        BOOST_CONSTEXPR duration  operator+() const;
        BOOST_CONSTEXPR duration  operator-() const ;
        
        duration& operator++()      {++rep_; return *this;}
        duration  operator++(int)   {return duration(rep_++);}
        duration& operator--()      {--rep_; return *this;}
        duration  operator--(int)   {return duration(rep_--);}
        duration& operator+=(const duration& d);
        duration& operator-=(const duration& d);
        duration& operator*=(const rep& rhs);
        duration& operator/=(const rep& rhs) ;
        duration& operator%=(const rep& rhs);
        duration& operator%=(const duration& rhs);
        
        static BOOST_CONSTEXPR duration zero();
        static BOOST_CHRONO_LIB_CONSTEXPR duration min ();
        static BOOST_CHRONO_LIB_CONSTEXPR duration max ();
    };    
  • chrono庫定義了常用的時間單位,比如hours、minutes、seconds、milliseconds等,但與date_time庫time_duration使用派生子類的方法不同,chrono庫通過在模板引數裡使用不同的ratio來定義時間單位,因而每一個時間單位都是不同的型別:
    typedef duration<boost::int_least64_t, nano> nanoseconds;    // at least 64 bits needed
    typedef duration<boost::int_least64_t, micro> microseconds;  // at least 55 bits needed
    typedef duration<boost::int_least64_t, milli> milliseconds;  // at least 45 bits needed
    typedef duration<boost::int_least64_t> seconds;              // at least 35 bits needed
    typedef duration<boost::int_least32_t, ratio< 60> > minutes; // at least 29 bits needed
    typedef duration<boost::int_least32_t, ratio<3600> > hours;  // at least 23 bits needed

  • 除了這些預定義的時間單位,我們也可以自己使用typedef來簡化duration型別,實現任意的時間解析度,比如:
typedef duration<long,   ratio<30>> half_min;  //半分鐘
typedef duration<int,    ratio<60*15>> quater;  //一刻鐘
typedef duration<double, ratio<3600*24>> day;   //一天

//typedef duration<int,60*60> my_hour;          //編譯錯誤,不能直接使用整數
//typedef duration<int,ratio<-10, 1000>> my_ms; //編譯錯誤,不能用負數

使用

duration就像是一個捆綁了算術型別的ratio,或者是一個帶有時間單位的數字型別

基本運算

  • duration內部只有一個成員變數req_,用來儲存時間的計數,支援各種算術運算----因為本質上它是一個算術型別。而時間單位則用ratio在編譯期表示,各種時間單位的轉換都是通過ratio在編譯期模板元計算完成的
  • duration的建構函式指定它的時間計數,表示一段req_ * ratio秒的時間長度,成員函式count()可以獲得內部的儲存的時間計數rep_:
    seconds      s(10);             // 10s,相當於10 * ratio<1>
    minutes      m(5);				// 5min, 相當於 5 * ratio<60>
    hours        h(1);              // 1h, 相當於 1 * ratio<3600>
    milliseconds ms(100);           // 100ms, 相當於 100 * ratio<1, 1000>

    assert(s.count() == 10);         //使用成員函式
    assert(ms.count() == 100);
  • duration可以像int、double一樣執行各種算數運算和比較運算,也能直接流輸出:
    s *= 3;
    s += seconds(30);
    s = s - seconds(20);
    assert(s < seconds(50));
    std::cout << s << std::endl;

boost:chrono

混用不同型別運算

  • 不同duration型別間混合運算要當心,小的時間單位可以直接於大的時間單位運算,因為大的時間單位可以自動轉換為小的時間單位,比如1hour=60min。但反過來不一定可以,如果大的時間單位的模板引數是整數而無法表示小時間單位,那麼就會發生編譯錯誤,提醒你不能混合運算
    seconds s(10);
    minutes m(5);

    s += m;				 //混合不同的時間單位運算
    cout << s << endl;   //輸出”310 seconds“

    //m+= s;             //編譯失敗,整數型別不能表示小數概念
  • 在duration的模板引數中使用double可以解決這個問題:
        seconds s(10);                               //10s
        typedef duration<double, ratio<60>> my_min;  //定義新的時間單位
        my_min m(5);                                 // 5分鐘
        m += s;                                      //混合不同的時間單位運算 
        cout << m << endl;                           //輸出”5.1667 minutes“

型別轉換

  • chrono庫提供一個模板函式duration_cast()來轉換不同的時間單位,它只是執行簡單的舍入取整
    using namespace std;
    seconds s(30);                               // 30s
    auto m = duration_cast<minutes>(s);           
    cout << m << endl;                           //"0 minutes"

    seconds s2(301);
    cout << duration_cast<minutes>(s2) << endl;  // "5 minutes"
  • chrono庫裡面還有三個C++標準之外的擴充套件函式floor()、ceil()、round()
    • floor():與duration_cast()相同,取下界,簡單的截斷處理
    • ceil():取上界
    • round():四捨五入操作
    using namespace std;
    seconds s(3600 + 50);                        //1h + 50s
    cout << floor<minutes>(s) << endl;             
    cout << ceil<minutes>(s) << endl;
    cout << round<minutes>(s) << endl;
    cout << round<hours>(s) << endl;    

boost:chrono

時鐘

  • 時鐘Clock是chrono裡的另一個概念,它確定了一個時間的起點(since)和時間單位(duration)。使用時鐘我們就可以處理計算機世界裡的時間。

  • chrono庫實現了C++標準裡定義的三個時鐘:

時鐘說明
system_clock計算機實際時間(system_clock的計時起點是1970-1-1)
steady_clock穩定的時鐘,不會因為系統時間調整而變化
high_resolution_clock高解析度的時鐘,但通常是前兩者的typedef
  • chrono庫還定義了四個度量程式執行時間的時鐘
時鐘說明
process_real_cpu_clock程序執行的實際時間
process_user_cpu_clock使用者CPU時間
process_system_cup_clock系統CPU時間
thread_clock執行緒執行的實際時間

各時鐘的介面基本相同,比如:

 class BOOST_CHRONO_DECL system_clock
  {
  public:
      typedef BOOST_SYSTEM_CLOCK_DURATION          duration;    //時間單位
      typedef duration::rep                        rep;         //計數單位
      typedef duration::period                     period;      //ratio
      typedef chrono::time_point<system_clock>     time_point;  //時間點
      BOOST_STATIC_CONSTEXPR bool is_steady =             false; //是否是穩定時鐘

      static BOOST_CHRONO_INLINE time_point  now() BOOST_NOEXCEPT;   //獲取當前時間
#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING
      static BOOST_CHRONO_INLINE time_point  now(system::error_code & ec);
#endif

	//轉換到C裡面的time_t結構
      static BOOST_CHRONO_INLINE std::time_t to_time_t(const time_point& t) BOOST_NOEXCEPT;
      static BOOST_CHRONO_INLINE time_point  from_time_t(std::time_t t) BOOST_NOEXCEPT;
  };
  • 時鐘用內部的四個typedef確定了可以產生的時間型別,is_steady用來區分時鐘是否會隨系統時間調整而改變
  • 時鐘類最重要的操作是now(),用來獲得當前的時間點time_point物件
  • system_clock額外提供與time_t結構的互換操作,而steady_clock 等時鐘不支援

使用輔助類clock_string的兩個靜態成員函式name()、since()可以獲取時鐘的描述資訊:

template<class CharT>
  struct clock_string<system_clock, CharT>
  {
    static std::basic_string<CharT> name();   //時鐘的名字
    static std::basic_string<CharT> since();  //時鐘的起點
  };
  • 使用方法:
template<typename T>
using clock_desc = clock_string<T, char>;

void case3()
{
    using namespace std;
    cout << clock_desc<system_clock>::name() << endl;
    cout << clock_desc<system_clock>::since() << endl;

    cout << clock_desc<steady_clock>::name() << endl;
    cout << clock_desc<steady_clock>::since() << endl;

    cout << clock_desc<process_real_cpu_clock>::name() << endl;
    cout << clock_desc<process_real_cpu_clock>::since() << endl;
}

int main()
{
    case3();
}

boost:chrono

時間點

時間點time_point與時鐘緊密關聯,它必須由一個時鐘產生,標記了自時鐘起點以來所經過的時間。

template <class Clock, class Duration>
class time_point
{
    public:
        typedef Clock                     clock;                 //內部型別定義
        typedef Duration                  duration;
        typedef typename duration::rep    rep;
        typedef typename duration::period period;
        typedef Duration                  difference_type;

    private:
        duration d_;                                               //時間長度

    public:
        BOOST_CONSTEXPR time_point() ;                            //建構函式
        BOOST_CONSTEXPR explicit time_point(const duration& d);

        BOOST_CONSTEXPR duration time_since_epoch() const;        //返回時間長度


#ifdef BOOST_CHRONO_EXTENSIONS
        BOOST_CONSTEXPR time_point  operator+() const {return *this;}
        BOOST_CONSTEXPR time_point  operator-() const {return time_point(-d_);}
        time_point& operator++()      {++d_; return *this;}
        time_point  operator++(int)   {return time_point(d_++);}
        time_point& operator--()      {--d_; return *this;}
        time_point  operator--(int)   {return time_point(d_--);}

        time_point& operator+=(const rep& r) {d_ += duration(r); return *this;}
        time_point& operator-=(const rep& r) {d_ -= duration(r); return *this;}

#endif

        time_point& operator+=(const duration& d) {d_ += d; return *this;}
        time_point& operator-=(const duration& d) {d_ -= d; return *this;}

        // special values

        static BOOST_CHRONO_LIB_CONSTEXPR time_point min()   //最前可以表示的時間點
        static BOOST_CHRONO_LIB_CONSTEXPR time_point max();  //最後可以表示的時間點
    };

使用方法:

void case3()
{
    typedef duration<double, ratio<3600*24>> day;
    using namespace std;
    auto tp1 = system_clock::now();
    cout << tp1 << endl;

    auto d = tp1.time_since_epoch();
    cout << duration_cast<hours>(d) << endl;
    cout << duration_cast<day>(d) << endl;

    auto tp2 = tp1 +minutes(1);
    cout << tp2 << endl;
}

boost:chrono

    using namespace std;
    typedef duration<double, ratio<3600*24>> day;
    auto tp = steady_clock::now();
    cout << tp << endl;

    auto d = tp.time_since_epoch();
    cout << round<minutes>(d) << endl;
    cout << round<hours>(d) << endl;

boost:chrono

其他

自定義字面值

C++14新增了h/s/m/ms等字面值,用來便捷的宣告duration變數。在C++11中我們同樣可以定義自己的字面量:

hours operator"" _h(unsigned long long n)
{
    return hours(n);
}

seconds operator"" _s(unsigned long long n)
{
    return seconds(n);
}

milliseconds operator"" _ms(unsigned long long n)
{
    return milliseconds(n);
}
void case3()
{
    using namespace std;
    auto h = 5_h;
    auto s = 45_s;
    auto ms = 200_ms;

    cout << h << "; "<< s << "; "<< ms << endl;
}

boost:chrono

時間點轉換為實際時間

  • system_clock提供靜態成員函式to_time_t(),可以把system_clock產生的time_point物件轉換為time_t型別。以time_t為基礎,我們既可以使用date_time庫的from_time_t()轉換到ptime物件,也可以用C的ctime()系統函式操作。
    auto tp = system_clock::now();
    auto t = system_clock::to_time_t(tp);

    cout << std::ctime(&t) << endl;
  • steady_clock等其他時鐘因為不與確定的現實時間點關聯,所以它們產生的time_point物件無法轉換為實際時間

高精度計時器

  • 仿照timer,我們也可以實現高精度的計時器,它最高可以精確到ns級
  • 我們避免計時系統受系統時間外部調整的影響,我們需要使用steady_clock,在物件建立時基類時間點,之後就可以隨時獲取當前時間,兩者相減得到時間
class steady_timer final {
private:
    typedef boost::chrono::steady_clock clock_type;    //定義時鐘型別
    typedef clock_type ::time_point  time_point_type;  //定義時間點型別
    typedef boost::chrono::milliseconds  duration_type; //使用微秒精度

    time_point_type m_start = clock_type::now();          //構造時記錄時間點
public:
    steady_timer() = default;
    ~steady_timer() = default;

public:
    void restart(){                                           //重啟計時器
        m_start = clock_type ::now();
    }

    duration_type elapsed() const {
        return round<duration_type>(clock_type::now() - m_start);  //將流逝時間轉換為微妙
    }
};

int main()
{
    using namespace std;
    steady_timer t;
    sleep(1);
    cout << t.elapsed() << endl;
}