前言
时间是宝贵的,我们无时无刻不在和时间打交道,这个任务明天下班前截止,你点的外卖还有5分钟才能送到,那个程序已经运行了整整48个小时,既然时间和我们联系这么紧密,我们总要定义一些术语来描述它,像前面说到的明天下班前、5分钟、48个小时都是对时间的描述,程序代码构建的程序世界也需要定义一些术语来描述时间。
今天要总结学习的是 std::chrono
库,它是 C++11
标准时从 boost
库中引入的,其实在 C++ 中还有一种 C
语言风格的时间管理体系,像我们常见的函数 time()
、clock()
、localtime()
、mktime()
和常见的类型 tm
、time_t
、clock_t
都是 C
语言风格的时间管理体系。
std::chrono
这个库之前接触的不多,C++20
标准都出了,C++11
引入的这个库还没怎么用过,整天和 time()
、 localtime()
、 tm
打交道,最近工作中换了项目,代码中出现了 std::chrono
的使用,是时候好好学习总结一下了。
chrono 的概况
- 头文件
#include <chrono>
- 命名空间
std::chrono
这个库从 C++11
引入标准之后,每个版本都有所修改,不过核心内容变化不是太大,他定义了三种主要类型,分别是 durations
、clocks
和 time points
,以及围绕这些类型的一些工具函数和衍生的定义。
chrono 的核心内容
duration
这个模板类用来表示时间间隔,我们知道时间的基本单位是秒,这个类的对象所表示的时间间隔也是以秒为单位的,它的定义如下:
template<class Rep, class Period = std::ratio<1>> class duration; |
Rep
表示一种数值类型,用来描述周期 Period
的数值类型,比如可以是 int
、float
等,而 Period
的类型是 std::ratio
,同样是一个模板类,实际表示的是一个有理数,像100、0、1/1000(千分之一)等等。
在 std
这个命名空间下有很多已经定义好的有理数,可以举几个常见的头文件 <ratio>
中的例子:
nano std::ratio<1, 1000000000> // 十亿分之一 micro std::ratio<1, 1000000> // 百万分之一 milli std::ratio<1, 1000> // 千分之一 centi std::ratio<1, 100> // 百分之一 deci std::ratio<1, 10> // 十分之一 deca std::ratio<10, 1> // 十 hecto std::ratio<100, 1> // 百 kilo std::ratio<1000, 1> // 千 |
比如我们想定义一个整数类型的100秒的时间间隔类型可以使用:
typedef std::chrono::duration<int, std::ratio<100,1>> my_duration_type; |
当然也可以简写成:
typedef std::chrono::duration<int, std::hecto> my_duration_type; |
如果我们想定义一个整数类型1分钟的时间间隔类型可以写成:
typedef std::chrono::duration<int, std::ratio<60,1>> my_minute_type; |
因为这种时、分、秒的时间表示在代码逻辑中很常用,所有在 std::chrono
命名空间下已经定义好了一些时间间隔类型:
std::chrono::nanoseconds duration</*signed integer type of at least 64 bits*/, std::nano> std::chrono::microseconds duration</*signed integer type of at least 55 bits*/, std::micro> std::chrono::milliseconds duration</*signed integer type of at least 45 bits*/, std::milli> std::chrono::seconds duration</*signed integer type of at least 35 bits*/> std::chrono::minutes duration</*signed integer type of at least 29 bits*/, std::ratio<60>> std::chrono::hours duration</*signed integer type of at least 23 bits*/, std::ratio<3600>> |
另外还有一个很重要的成员函数 count()
,用来获得指定的时间间隔对象中包含多少个时间周期,接下来可以写个例子理解一下,我们用 duration
这个模板类来表示一下5分钟和12小时,看看他应该怎么使用,对于5分钟你可以看成是 5 个 1 分钟或者 1 个 5 分钟,或者更变态你可以看成 2.5 个 2 分钟,而 12 小时一般会看成是 12个 1 小时,你当成 0.5 个 1 天也是可以的:
#include <chrono> #include <iostream> int main() { // 以下为5分钟表达 std::chrono::minutes minute1{5}; // 5个1分钟 std::chrono::duration<int, std::ratio<5*60, 1>> minute2{1}; // 1个5分钟 std::chrono::duration<double, std::ratio<2*60, 1>> minute3{2.5}; // 2.5个2分钟 std::cout << "minutes1 duration has " << minute1.count() << " ticks\n" << "minutes2 duration has " << minute2.count() << " ticks\n" << "minutes3 duration has " << minute3.count() << " ticks\n"; // 一下为12小时表达 std::chrono::hours hours1{12}; // 12个1小时 std::chrono::duration<double, std::ratio<60*60*24, 1>> hours2{0.5}; // 0.5个1天 std::cout << "hours1 duration has " << hours1.count() << " ticks\n" << "hours2 duration has " << hours2.count() << " ticks\n"; // 使用 std::chrono::duration_cast<T> 将分钟间隔转化成标准秒间隔 std::cout << "minutes1 duration has " << std::chrono::duration_cast<std::chrono::seconds>(minute1).count() << " seconds\n"; } |
上述代码中还使用了 std::chrono::duration_cast<T>()
函数,用于各种时间间隔的换算,运行结果如下:
minutes1 duration has 5 ticks minutes2 duration has 1 ticks minutes3 duration has 2.5 ticks hours1 duration has 12 ticks hours2 duration has 0.5 ticks minutes1 duration has 300 seconds |
clock
从名字可以看出这个类叫做时钟,时钟是用来看时间和计时的,常用的两个类是 system_clock
和 steady_clock
,在 C++20
标准中又加入了多种内容,现在我们先来看看这两个常用类。
从这一部分开始类的定义让人有些迷糊,其实 clock 引用了 std::chrono::duration
和后面要说的 std::chrono::time_point
, 而 std::chrono::time_point
又引用了 std::chrono::duration
和现在要讲的 std::chrono::system_clock
、 std::chrono::steady_clock
,如果只看定义很容易被绕晕,所以还是先做个练习实验一下。
system_clock
这个类被称为系统内时钟,当修改系统时钟时可能会改变其单调递增的性质,静态成员函数有 now()
、to_time_t()
、from_time_t()
三个,关于它的单调性被修改举个例子,一般认为时间一直是递增的,但是当你现在调用一次函数 now()
,然后把时间往过去调1天,然后再调用 now()
函数,就会发现新得到的时间“变小”了。
也因为这样它会受到 NTP(Network Time Protocol,网络时间协议)的影响,但是不会受时区和夏令时的影响(其实很多国家早就废除夏令时了)。
下面写个例子练习一下,例子中使用了 now()
、to_time_t()
、from_time_t()
三个函数,不清楚的时候可以对照一下:
#include <chrono> #include <iostream> int main() { std::chrono::duration<int, std::ratio<60*60*24> > one_day(1); // 根据时钟得到现在时间 std::chrono::system_clock::time_point today = std::chrono::system_clock::now(); std::time_t time_t_today = std::chrono::system_clock::to_time_t(today); std::cout << "now time stamp is " << time_t_today << std::endl; std::cout << "now time is " << ctime(&time_t_today) << std::endl; // 看看明天的时间 std::chrono::system_clock::time_point tomorrow = today + one_day; std::time_t time_t_tomorrow = std::chrono::system_clock::to_time_t(tomorrow); std::cout << "tomorrow time stamp is " << time_t_tomorrow << std::endl; std::cout << "tomorrow time is " << ctime(&time_t_tomorrow) << std::endl; // 计算下个小时时间 std::chrono::system_clock::time_point next_hour = today + std::chrono::hours(1); std::time_t time_t_next_hour = std::chrono::system_clock::to_time_t(next_hour); std::chrono::system_clock::time_point next_hour2 = std::chrono::system_clock::from_time_t(time_t_next_hour); std::time_t time_t_next_hour2 = std::chrono::system_clock::to_time_t(next_hour2); std::cout << "tomorrow time stamp is " << time_t_next_hour2 << std::endl; std::cout << "tomorrow time is " << ctime(&time_t_next_hour2) << std::endl; return 0; } |
运行结果如下:
now time stamp is 1586662332 now time is Sun Apr 12 11:32:12 2020 tomorrow time stamp is 1586748732 tomorrow time is Mon Apr 13 11:32:12 2020 tomorrow time stamp is 1586665932 tomorrow time is Sun Apr 12 12:32:12 2020 |
steady_clock
这是一个单调时钟,一旦启动之后就与系统时间没有关系了,完全根据物理是时间向前移动,成员函数只有一个 now()
,通常可以用来计时,使用方法与 system_clock
相比简单许多,下面写个小例子。
#include <chrono> #include <iostream> int main() { // 先记录程序运行时间 std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); volatile int nDstVal, nSrcVal; for (int i = 0; i < 1000000000; ++i) nDstVal = nSrcVal; // 做差值计算耗时 std::chrono::duration<double> duration_cost = std::chrono::duration_cast< std::chrono::duration<double> >(std::chrono::steady_clock::now() - start); std::cout << "total cost " << duration_cost.count() << " seconds." << std::endl; return 0; } |
运行结果如下:
total cost 1.9424 seconds. |
time point
这个类与 duration
类似,同样是模板类,表示具体的时间点,比如今天 18:00 开饭,明天上午 10:00 发版本,今年 5 月 1 日可能因为疫情不让出去玩了,像这些具体的时间点可以使用 std::chrono::time_point
来表达,它的定义如下:
template<class Clock, class Duration = typename Clock::duration> class time_point; |
首先这个类是在 std::chrono
这个命名空间下,但是你会经常看到以下这种写法:
std::chrono::system_clock::time_point today = std::chrono::system_clock::now(); std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); |
好像 time_point
又在 std::chrono::system_clock
和 std::chrono::steady_clock
范围内,实际上这两个范围内的 time_point
引用的是 std::chrono::time point
,看看 std::chrono::system_clock
的定义能明白一些。
class system_clock { public: using rep = /*see description*/ ; using period = ratio</*unspecified*/, /*unspecified*/ >; using duration = chrono::duration<rep, period>; using time_point = chrono::time_point<system_clock>; static constexpr bool is_steady = /*unspecified*/ ; static time_point now() noexcept; // Map to C API static time_t to_time_t (const time_point& t) noexcept; static time_point from_time_t(time_t t) noexcept; }; |
对照上面的定义可以知道,std::chrono::system_clock::time_point
实际上 std::chrono::time_point<system_clock>
,这几个时间类的定义相互引用,看到这一部分的时候一定不要烦躁,一步步推导分析其中的关系。
time_point
这个类有一个成员函数 time_since_epoch()
用来获得 1970-01-01 00:00:00
到 time_point
时间经过的 duration
, 返回的 duration
的单位取决于 timepoint
定义时的 duraion
的单位,不过你也可以得到 duration
之后使用 std::chrono::duration_cast<T>()
函数来转化。
#include <chrono> #include <iostream> int main() { // 获得epoch 和 now 的时间点 std::chrono::time_point<std::chrono::system_clock> epoch = std::chrono::time_point<std::chrono::system_clock>{}; std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now(); // 显示时间点对应的日期和时间 time_t epoch_time = std::chrono::system_clock::to_time_t(epoch); std::cout << "epoch: " << std::ctime(&epoch_time); time_t today_time = std::chrono::system_clock::to_time_t(now); std::cout << "today: " << std::ctime(&today_time); // 显示duration的值 std::cout << "seconds since epoch: " << std::chrono::duration_cast<std::chrono::seconds>(epoch.time_since_epoch()).count() << std::endl; std::cout << "today, ticks since epoch: " << now.time_since_epoch().count() << std::endl; std::cout << "today, hours since epoch: " << std::chrono::duration_cast<std::chrono::hours>(now.time_since_epoch()).count() << std::endl; return 0; } |
运行结果如下:
epoch: Thu Jan 1 08:00:00 1970 today: Sun Apr 12 12:30:04 2020 seconds since epoch: 0 today, ticks since epoch: 1586665804624992500 today, hours since epoch: 440740 |
从运行结果来看,epoch 的时间点是 Thu Jan 1 08:00:00 1970
,为什么不是 1970-01-01 00:00:00
呢?那是因为我们在东8区,格林威治时间为1970-01-01 00:00:00
的时候,我们的时间就是 Thu Jan 1 08:00:00 1970
,这样看来 std::ctime()
这个函数考虑了时区的影响,相同的代码如果在韩国同时运行得到的可能就是 epoch: Thu Jan 1 09:00:00 1970
。