2017年2月4日 星期六

c++ 11 的 chrono, 時間程式庫

chrono 應該是個令人陌生的單字, 通常我們熟悉的是 time, date 之類的單字, 這個單字是 "時間" 的意思。所以這個系列的 class 是用來處理日期與時間。

chrono 還真不是普通複雜, 我只不過是要把 system_clock::now(); 以數值的方式印出來, 結果花了 3 小時才完成目的。 除了概念上的抽象外, template 語法也讓這個程式庫的難度增加不少, 我懷疑 c++ 把簡單的事情搞複雜了。

auto 很好用, 但它隱藏了複雜的型別宣告, 對於理解 chrono 的這些東西造成了阻礙, 書上介紹都用 auto, 這讓真相又模糊了一些。

先介紹 chrono 的相關名詞, 很重要, 一定要搞懂這些概念, 否則很難理解 chrono。

system_clock::now() 會傳回 time_point, 所以目標是怎麼把這個 time_point 印出來。

time_point 是 duration + epoch, 所以要搞懂這 2 個。

duration 是 (tick 最大長度, tick 的單位) 的組合, 而秒是用 ratio class 表示, 所以又要知道 ratio 的用法, 由於這些東西加在一起, 造成很難理解 chrono。

list 1. L14 定義了一個 duration, tick 最大長度是 int 大小, 1 個 tick 是 0.001 (1/1000) 秒。

list 1. 定義一個 duration
14 duration<int, ratio<1, 1000>> du{2};
15 cout << du.count() << endl;

du{2} 表示有 2 個 tick, 所以這個 du (duration) 表示 0.001 * 2 = 0.002 秒。

clock 則是定義一個 epoch 和 tick 週期, epoch 是起始點, 每個 clock 都可以有不同的起始點, unix 可能是 1970/1/1, 0 時 0 分 0 秒。

好了, 知道了這些, 那該怎麼印出 system_clock::now() 呢?

減去 epoch 求出 duration, 再印出這個 duration 即可。

所以這個 now 並不是直接印出 now 的值, 而是從 now 到 epoch 的 duration, 如果 now 是 1970/1/1 - 0 時 0 分 1 秒, epoch 是 1970/1/1, 0 時 0 分 0 秒, 那這個 duration 的數值是多少呢?

不見得是 1, 有可能是 1000, 有可能是 1000000, 因為 duration 還有一個 ?? 秒的單位。這個時間是 "秒", 如果 duration 是以 1 秒為單位, 這個數值就是 1, 如果以 0.001 秒為單位, 就是 1000。

2 個 time_point 可以相減, 得到的是什麼呢? 是 time_point 嗎? 不是, 是 duration, 但是這個 duration 的單位是什麼呢? 不知道沒關係, 透過 duration_cast 轉成我們要的單位即可。

L46 就是在做這樣的轉換, 我把它轉成 us 為單位。

沒有使用 auto 的 template 變數看來嚇人, 不過 template 語法就是這麼嚇人, 而完整寫出整個型別, 才更清楚自己寫的程式碼是什麼意思。

標準程式庫的 header file 定義了 (/usr/include/c++/6/ratio, /usr/include/c++/6/chrono) microseconds, micro

ratio:517:  typedef ratio<1, 1000000> micro;
chrono:530:    typedef duration<int64_t, micro>      microseconds;

為了搞懂這些單位, 我沒有使用標準程式庫的 micro, microseconds, 這樣讓我更清楚 duration 的單位表示法。

t2.cpp
 1 #include <iostream>
 2 #include <chrono>
 3 #include <vector>
 4 #include <string>
 5 
 6 using namespace std;
 7 using namespace std::chrono;
 8 
 9 #include <unistd.h>
10 #include <ctime>
11 
12 string as_string(const system_clock::time_point &tp)
13 {
14   time_t t = system_clock::to_time_t(tp);
15   string ts = ctime(&t);
16   ts.resize(ts.size()-1);
17   cout << "time_t t: " << t << endl;
18   cout << "ts: " << ts << endl;
19   return ts;
20 }
21 
22 int main(int argc, char *argv[])
23 {
24   //duration<int, ratio<1, 1000>> d{2};
25   //cout << d.count() << endl;
26   time_point<system_clock> epoch = time_point<system_clock>{};
27   time_point<system_clock> tp = system_clock::now();
28 
29   as_string(epoch);
30 
31   time_t t = system_clock::to_time_t(tp);
32 
33   string ts = ctime(&t);
34   cout << "now: " << ts << endl;
35 
36   time_point<steady_clock> tp1 = steady_clock::now();
37 
38   time_point<steady_clock> steady_epoch = time_point<steady_clock>{};
39 
40   //duration<int, ratio<1,1000000>> d = duration_cast<microseconds>(epoch - tp);
41   //duration<int, ratio<1,1000000>> d = duration_cast<microseconds>(tp - epoch);
42  
43   typedef duration<unsigned long long, ratio<1,1000000>> unit;
44   //typedef duration<int, ratio<1,1000000>> unit;
45   //typedef microseconds unit;
46   unit d = duration_cast<unit>(tp - epoch);
47   cout << d.count() << endl;
48 
49   return 0;
50 }

那可以印出 epoch 或是一個 time point 的值嗎? 可以的, epoch 其實對應到一個 time_t 值, 這個值不用印也知道是 0, 但你還是想印出來確認吧? 該怎麼印出這個值呢?

L14 在做這樣的轉換。time_t 的時間單位是什麼呢? 通常是以 unix epoch 19700101 為計算基準的秒數, 由於 time point 就是 19700101, 所以就是 0。

test environment:
debian x86_64

ref:
Why is the Win32 epoch January 1, 1601? (win32 的 epoch 和 unix 不同)
C++ 標準庫 5.7 p 143
The C++ Programming Language國際中文版 第四版

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。