「小满」C++ chrono 库使用学习笔记
本文是我学习 C++ chrono 库时的学习、思考的临时笔记。很多地方可能存在谬误,请读者带着批判性的目光阅读。
学习 chrono
库重要点是了解其中三个重要概念及它们之间的联系:
- 时钟 (clock)
- 时间点 (time_point)
- 时间间隔 (duration)
上面三个重要概念是我依靠自己感觉总结出来的,名字也是自己起的,可能有失偏颇,但大体应该如此。个人觉得这三个名字也可以很直观地来理解他们的意义。在 C++ 的 chrono
库中,他们相互之间的联系也很直觉,如时钟可以获得时间点,两个时间点的差为时间间隔,时间点加上一个时间间隔即是另外一个时间点。
时钟 (clock)
时钟的用途就是用来获取时间点。chrono
标准库中提供了三种(我目前知道的)时钟,分别为:
std::chrono::system_clock
:系统时钟,用于表示现实世界时间的时钟,如现在是几点等。该时钟不保证单调递增,受系统时间的影响,当系统时间发生调整时,system_clock
的读数可能会发生跳变 / 倒退。std::chrono::steady_clock
:单调递增时钟,主要用于测量时间间隔,如程序运行计时等。不受系统时间的影响。类似于clock_gettime
中的CLOCK_MONOTONIC
。std::chrono::high_resolution_check
:最高精度时钟,拥有最高的时间精度,适合用于性能计时和测量时间间隔。
在某些实现中,high_resolution_clock
可能是system_clock
或steady_clock
的别名。因此,它可能具有system_clock
或steady_clock
的特点,具体取决于实现。
这三种时钟类型的精度和特性可能因平台和实现而异。
对于时钟来说,最常用的成员函数应该是 now()
,即获取当前的时钟时间。
对于 system_clock
,还有两个较为常用的成员函数,为 from_time_t
和 to_time_t
,即通过 C 风格下的 time_t
(即以秒计的时间戳),来获取该时间戳下的时间点 (time_point)。或将时间点转换为 C 风格下的时间戳。
时间点 (time_point)
时间点即为特定时钟下的某个时刻。也就是说,chrono
库中,如果要指定一个时间,需要确定三个元素。
- 是哪种时钟下的时间点
- 这种时钟下的纪元(即零点时间)是什么,(如我们熟知的
UTC 1970-01-01 00:00:00
) - 该时间点距离纪元的时间间隔是多久。
时间间隔 (duration)
时间间隔即两个时间点相距多久。chrono
库中,要表示时间间隔,要确定以下两点:
- Rep:数据表示方式(存储类型),如
int
,float
,uint64_t
。 - Period:时间间隔单位,如秒、毫秒、分钟。
在库内实现中,以std::ratio
为表示,如std::ratio<1, 1>
(即1
),表示秒;std::ratio<1, 1000>
,即0.001
,表示毫秒;std::ratio<60, 1>
,即60
,表示分钟。
对于常用的时间间隔类型,chrono
中已包装好,拿来即用就可,如 std::chrono::seconds
表示秒,std::chrono::hours
表示小时。 std::chrono::seconds dur {5}
,这样,我们就定义了一个 5 秒的时间间隔变量。在 C++ 20 中,又加入了天(days
)、月(months
)、年(years
)的类型。
从 C++14 开始,chrono
加入了 literals
,可以帮我们更方便地表达时间间隔,更符合人类的语义习惯。如 auto dur = 3s
,就定义了一个 3 秒的时间间隔。(有些小吐槽,见后面的吐槽部分)
不同时间间隔类型之间当然是可以相互转换的,它们通过 std::chrono::duration_cast
来进行转换,如,auto dur = std::chrono::duration<std::chrono::minutes>(120s)
,这样,我们可以将 120 秒转换为 2 分钟。
对一个时间间隔变量使用 count
成员函数,可以获得其单位下的间隔时间,如 (120s).count()
,我们则可以获得到 120 。
三个概念关系
时钟可获得时间点。
时间点是特定时钟下距离时钟纪元一定时间间隔的时刻。
时间点 加 / 减 时间间隔得到另一个时间点。时间间隔之间可以加减,得到新的时间间隔。时间间隔也可以乘除一个倍数,用于放大 / 倍缩该时间间隔。
这些都很直觉。C++ chrono
中的类型设计还是很符合人类语义习惯的,这也是我很喜欢 C++ 的一个地方。
使用 fmtlib 格式化时间
TODO
一些小吐槽
上面我们提到,C++11 中,就有了 seconds
、minutes
、hours
等类型的时间时间,但是对于天、月、年这个级别的时间间隔的支持,拖延到了 C++20 才进入标准。
在 chrono
的 literals
支持上,C++14 中我们可以方便地使用 60s
,30min
,12h
来表示时间间隔,但却不支持天、月、年。C++ 20 中加入了 d
, y
,来表示天和年,但注意,这是日期上的日和年,而不是时间间隔上的!(如 23d
表示某一个月的 23 号,而非时间长度上的 23 天)年可以理解,毕竟不同年份的时间不一致,但没有加入天数上的字面词,在代码风格上不能与秒、分钟达成统一,(如,我表达 3 秒可以写作 3s
,表达 10 天却要写成 days {10}
)我感觉还是有些不完美、略有遗憾吧。
我们可以通过 24h * 10
来表达 10 天,却不能够使用 (10 * 24)h
,我觉得也是在语言设计上的一点缺陷,后者这明明是 constexpr
的,也有些遗憾吧。
感悟
我之所以写这篇学习笔记,是因为在新的工作中,我需要记录时间,用于计算特定时间内处理了多少条数据等,并打印出日志来进行方案评估。
我自诩为看过一些 C++ 书的,了解一些 C++,却从未正式地写过 C++。于是知道有这种 API
,却没有真实地使用过这个 API
,没有实践的经历及由此而来的肌肉记忆。因此在写代码时,不能够很流畅地写出,感觉很生涩。真实地感受到了 “纸上得来终觉浅,绝知此事要躬行。”
我们总说,编程语言没那么重要,数据结构、算法、系统架构这些才是重要的。这句话我是认可的,但令一方面,我觉得对程序员来说,编码是我们表达我们思想的方式。语言决定者对语言底层思想的设计,我们对该语言的理解、应用能力,(一定程度上)决定了我们在写代码时表达我们思想的能力。
写这篇笔记时,刚好是小满(虽然写完不是了)。我很喜欢这个节气,万物至此,小得圆满,很有中国传统哲学的智慧。