「小满」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,没有实践的经历及由此而来的肌肉记忆。因此在写代码时,不能够很流畅地写出,感觉很生涩。真实地感受到了 “纸上得来终觉浅,绝知此事要躬行。”
我们总说,编程语言没那么重要,数据结构、算法、系统架构这些才是重要的。这句话我是认可的,但令一方面,我觉得对程序员来说,编码是我们表达我们思想的方式。语言决定者对语言底层思想的设计,我们对该语言的理解、应用能力,(一定程度上)决定了我们在写代码时表达我们思想的能力。
写这篇笔记时,刚好是小满(虽然写完不是了)。我很喜欢这个节气,万物至此,小得圆满,很有中国传统哲学的智慧。