C++ duration(STL duration)模板用法詳解

2020-07-16 10:04:28
duration (持續時間) 是定義為時間刻度數的時間間隔,可以指定一個時間刻度是多少秒。因此,時間刻度是衡量時間長短的基礎。duration 模板的範例型別的物件定義了 duration。時間刻度所表示的預設時間間隔是 1 秒,但可以將它定義為更多秒或秒幾分之一。例如,如果定義時間刻度為 3600 秒,但意味著 10 個 duration 就是 10 個小時;也能夠定義時間刻度為一秒的十分之一,在這種情況下,10 個 duration 表示 1 秒。

定義 duration

chrono 標頭檔案中的 std::chrono::duration<T,P> 模板型別表不 duration。模板引數 T 是值的型別,一般是基本的算術型別,並且引數 P 對應的值是時間刻度所表示的秒數,它所對應的值為 1 秒。必須用 ratio 型別指定 P 的值,預設為 ratio<1>。下面是 duration 的一些範例:
std::chrono::duration<int,std::milli> IBM650_divide {15}; // A tick is 1 millisecond so 15 milliseconds std::chrono::duration<int> minute {60}; // A tick is 1 second by default so 60 seconds
std::chrono::duration<double, ratio<60>>hour {60}; // A tick is 60 seconds so 60 minutes
//A tick is a microsecond so 1 millisecond std::chrono::duration<long, std::micro> millisec {1000L};
//A tick is fifth of a second so 1.1 seconds
std::chrono::duration<double, ratio<1,5>> tiny {5.5};
第 1 條語句使用從 ratio 標頭檔案中獲取的對應於 ratio<1,1000> 的別名 milli。第 2 條語句省略了第二個模板引數的值,因此是 ratio<1>,這意味著持續時間以 1 秒為單位。

在第 3 條語句中,ratio<60> 模板引數值指定了時間刻度為 60 秒,因此 hour 物件的值是按分鐘測量的,它的初值表示 1 小時。

第 4 條語句使用了 ratio 頭定義的 micro 型別來定義 ratio<1, 1000000>,因此 tick 是毫秒,millisec 變數有一個表示毫秒的初值。最後一條語句定義了一個時間刻度為 1/5 秒的物件,初值是 5.5 的五分之一,它是 1.1 秒。

chrono 標頭檔案為有整數型別值的常用持續時間型別在 std::chrono 名稱空間中定義了別名。標準別名是:

nanoseconds<integer_type, std::nano> microseconds<integer_type, std::micr>milliseconds<integer_type, std::milli> seconds<integer_type> minutes<integer_type, std::ratio<60>> hours<integer_type, std::ratio <3600>>

這些別名中,每個整數型別的持續時間值都取決於實現,但 C++14 標準要求 duration 至少為 292 年,可以是正數或負數。在筆者系統上小時和分鐘型別將 duration 儲存為 int 型別,將其他的儲存為 long long 型別。因此前面的程式碼段可以這樣定義 millisec 變數:
std::chrono::microseconds millisec {1000}; // Duration is type long long on my system
當然,這個定義和原始定義不同。變數的初值表示的是相同的時間間隔(1 毫秒)但這裡持續時間的單位是 1 毫秒,而在先前的程式碼中是 1 微秒。前面的 millisec 的定義許更精確地表示持續時間。

所有可以應用到 duration 物件的算術運算子,都可以被用到左運算元是 duration 物件的複合賦值中,+= 和 -+ 這些運算的右運算元必須是 duration 物件。*= 和 /= 的右運算元必須和作為左運算元的時間刻度數有相同型別的數值,或者可以隱式轉換為數值。%= 的右運算元可以是 duration 物件或數值。它們中的每一個都能產生我們期望的結果。例如下面使用 += 的程式碼:
std::chrono:imilliseconds millisec {1}; // Duration is also type long long on my system
第一個 += 運算的運算元為相同型別,作為右操作的物件所儲存的值會被加到左運算元上。第二個 += 運算的運算元為不同型別,但右運算元會被隱式轉換為左操作的型別。這是可能的,因為轉換是向有較短時間刻度的 duration 型別轉換的。如果向相反的方向轉換就不行,所以不能在 += 運算中用 long_time 作為左運算元、用 short_time 作為右運算元。

duration之間的算術運算

可以將字首或字尾自增和自減運算子應用到 duration 物件上,並且可以通過呼叫成員函數 count() 來得到時間刻度數。下面是範例程式碼:
std::chrono::duration<double, ratio<1, 5>> tiny {5.5}; // Measured in 1/5 second
std::chrono::microseconds very_tiny {100}; // Measured in microseconds
++tiny;
very_tiny--;
std::cout << "tiny = " << tiny.count()<< " very_tiny = " << very_tiny.count()<< std::endl;    // tiny = 6.5 very_tiny = 99
可以將任何二元算術運算子 +、-、*、/、% 應用到 duration 物件上,會得到一個 duration 物件作為結果。這些都是作為非成員運算子函數實現的。下面是一個範例:
std::chrono::duration<double, ratio<1, 5>> tiny {5.5}; std::chrono::duration<double, ratio<l, 5>> small {7.5};
auto total = tiny + small;
std::cout << "total = " << total.count() << std::endl;
// total = 13
算術運算子也可以用不同型別的 std::chrono::duration<T,P> 模板範例作為運算元,模板的兩個引數都可以不同。這是通過使用 common_type<class... T> 模板的特化將兩個運算元轉換為它們的共有型別來實現的,common_type<class... T> 定義在 type_traits 標頭檔案中。對於 duration<T1,P1> 和 duration<T2, P2> 型別的引數,返回值為 duration 型別 duration<T3,P3>。T3 是 T1 和 T2 的共有型別。這些型別是通過將算術運算應用到這些型別的值來得到的。

P3 是 P1 和 P2 的最大公因數。有一個範例會更清楚一些:
std::chrono:imilliseconds ten_minutes {600000}; // A tick is 1 millisecond so 10 minutes
std::chrono::minutes half_hour {30}; // A tick is 1 minute so 30 minutes
auto total = ten_minutes + half_hour; // 40 minutes in common tick period
std::cout <<"total = " << total.count()<< std::endl;
// total = 2400000
加法的結果是 40 分鐘,因此可以推斷出 total 是毫秒型別的物件。下面是另一個範例:
std::chrono::minutes ten_minutes {10};  // 10 minutes
std::chrono::duration<double, std::ratio<1, 5>> interval {4500.0}; // 15 minutes
auto total_minutes = ten_minutes + interval;
std::cout << "total minutes = " << total_minutes.count()<< std::endl;
// total minutes = 7500
total_minutes 的值是 double 型別。我們知道結果必定是 25 分鐘,也就是 1500 秒;結果為 7500,因此時間刻度的長度為 ratio<1,5>,即 1/5 秒。最好盡可能地避免對混合的 duration 型別進行算術運算,因為很容易不知道時間刻度是什麼。

所有可以應用到 duration 物件的算術運算都可以用到左運算元是 duration 的複合賦值中。+= 和 -= 的右運算元必須是 duration 物件。*= 和 /= 的右運算元必須和左運算元的時鐘刻度型別相同,或者可以隱式轉換為那種型別。%= 的右運算元可以是 duration 物件或數值。它們中的每一個都能產生我們想要的結果。例如,下面是使用 += 的範例:
std::chrono::minutes short_time {20};
std::chrono::minutes shorter_time {10};
short_time += shorter_time; // 30 minutes
std::chrono::hours long_time {3}; // 3hrs = 180 minutes
short_time += long_time;
std::cout << "short_time = " << short_time.count() << std::endl;
// short_time = 210
第一個 += 運算的運算元都為相同型別,因此右操作物件儲存的值會被加到左運算元上。第二個 += 運算的運算元為不同型別,但是右運算元可以隱式轉換為左運算元的型別。這是可能的,因為轉換的是短時鐘週期的 duration 型別。相反,就不能轉換,因此不能在用 += 時以 long_time 作為左運算元,以 short_time 作為右運算元。

duration型別之間的轉換

通常,一個 duration 型別總是可以被隱式轉換為另一個 duration 型別,如果它們都是浮點型 duration 值的話。當源型別的時鐘週期是目的型別的時鐘週期的整數倍時,只能對整數值進行隱式轉換。下面是一些範例:
std::chrono::duration<int, std::ratio<1, 5>> d1 {50}; // 10 seconds
std::chrono::duration<int, std::ratio<1, 10>> d2 {50}; // 5 seconds
std::chrono::duration<int, std::ratio<l, 3>> d3 {45}; // 15 seconds
std::chrono::duration<int, std::ratio<1, 6>> d4 {60}; // 10 seconds
d2 += d1; // OK - implicit conversion of d1
d1 += d2; // Won't compile 1/10 not a multiple of 1/5
d1 += d3; // Won't compile 1/3 not a multiple of 1/5
d4 += d3; //OK - implicit conversion of d3
可以顯式地使用 dumtion_cast 模板進行強制轉換。下面是一個範例,假設 d1 和 d2 都有上述程式碼定義的初值:
d1 += std::chrono::duration_cast<std::chrono::duration<int, std::ratio<l, 5>>>(d2);
std::cout << d1.count() << std::endl; // 75 - i.e.15 seconds
第一條語句會用 duration_cast 來使 d2 加到 d1 的運算能夠進行。在這個範例中,結果是準確的,但並不總是如此。例如:
std::chrono::duration<int, std::ratio<1, 5>> d1 {50}; // 10 seconds
std::chrono::duration<int, std::ratio<1, 10>> d2 {53}; // 5.3 seconds
d1 += std::chrono::duration_cast<std::chrono::duration<int, std::ratio<1, 5>>>(d2);
std::cout << d1.count() << std::endl; // 76 - i.e. 15.2 seconds
不能將 duration 值 d1 和 d2 的和表示成 0.2 秒的整數倍,因此結果值稍微超出了。如果 d2 的值是 54,得到的正確結果應該是 77。

duration 型別支援賦值,因此可以將一個 duration 物件的值賦給另一個 duration 物件。如果與這一節開始時描述的條件相符,就可以使用隱式轉換;否則需要對右運算元顯式轉換。例如,可以這樣寫:
std::chrono::duration<int, std::ratio<1, 5>> d1 {50}; // 10 seconds
std::chrono::duration<int, std::ratio<1, 10>> d2 {53}; // 5.3 seconds
d2 = d1;  // d2 is 100 = 10 seconds

比較 duration

有一整套完整的運算子可以用來比較兩個 duration 物件。它們都是由非成員函數實現的,允許比較不同型別的 duration 物件。這個過程可以確定運算元共同的時鐘週期,當表示成相同的時鐘週期時,就可以比較 duration 的值。例如:
std::chrono::duration<int,std::ratio<1, 5>> d1 {50}; // 10 seconds
std::chrono::duratior<int, std::ratio<1, 10>> d2 {50}; // 5 seconds
std::chrono::duration<int, std::ratio<1, 3>> d3 {45}; // 15 seconds
if ((d1 - d2) == (d3 - d1))
    std::cout << "both durations are "<<std::chrono::duration_cast<std::chrono::seconds〉(d1 - d2).count() << " seconds" << std::endl;
這裡顯示了從算術運算得到的 duration 物件的比較方法。它們是相等的,當然也會產生這樣的輸出。seconds 型別的強制轉換允許秒數表示成整數,而不用管結果為 duration 型別。如果想使用非整數的秒數值,可以強制轉換為 dumtion<dcmble> 型別。

duration 常數

chrono 標頭檔案中定義了可以讓我們指定 duration 物件的常數的運算子。這些運算子被定義在名稱空間 std::literals::chrono_literals 中,名稱空間 literals 和 chrono_literals 都是內聯的。有了下面的宣告之後,就可以對 duration 常數使用常數運算子:
using namespace std::literals::chrono_literals;
但是,如果指定了下面的宣告,上面的宣告會被自動包含:
using namespace std::chrono;
可以將 duration 常數指定為整數或浮點值,字尾指定了時鐘週期。這裡有 6 個可以使用的字尾:
  • h 是小時,例如 3h 或 3.5h。
  • min 是分鐘,例如 20min 或 3.5min。
  • s 是秒,例如 10s 或 1.5s。
  • ms 是毫秒,例如 500ms 或 1.5ms。
  • us 是微秒,例如 500us 或 0.5uso
  • ns 是納秒,例如 2ns 或 3.5ns。

如果一個 duration 常數有整數值,它會從這些被定義在 chrono 標頭檔案中的常數得到一個合適的別名。因此 24h 是一個 std::chrono::hours 型別的常數,25ms 的型別是 std::chrono::milliseconds。如果常數的值不是整數,這個常數會是浮點值型別的 duration 型別。浮點值的週期取決於字尾;對於字尾 h、min、s、ms、us 和 n,時鐘週期分別為 ratio<3600>、ratio<60>、 ratio<1>、milli、micro 和 nano。

下面舉例說明使用它們的一些方式:
using namespace std::literals::chrono_literals;
std::chrono::seconds elapsed {10}; // 10 seconds
elapsed += 2min; // Adding type minutes to type seconds: 130 seconds
elapsed -= 15s; // 115 seconds
如範例所示,當需要改變間隔的長短時,duration 常數是非常有用的。需要記住的是,為了能夠進行算術運算,作為右運算元的時鐘週期必須是作為左運算元的時鐘週期的整數倍。例如:
elapsed += 100ns; // Won't compile!
變數 lapsed 的時鐘週期為 1,不能向它加一個週期小於 1 的 duration。

可以用常數將同型別的變數定義為常數。例如:
auto some_time = 10s;   // Variable of type seconds, value 10
elapsed = 3min - some_time;// Set to difference between literal and variable: result 170
some_time *= 2; // Doubles the value - now 20s
const auto FIVE_SEC = 5s; // Cannot be changed
elapsed = 2s + (elapsed - FIVE_SEC)/5;
// Result 35
這裡,some_time 是一個 seconds 型別的變數,它的型別為 duration<long long,ratio<1>>,值為 10。第 3 條語句說明可以改變 some_time 的值。FIVE_SEC 是 const seconds 型別的值,因此不能改變它的值。最後一條語句展示了一個包含一個 duration 常數、一個 duration 變數、一個常數型別的 seconds 物件,以及一個整數常數的算術表示式。