「小满」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_clocksteady_clock 的别名。因此,它可能具有 system_clocksteady_clock 的特点,具体取决于实现。

这三种时钟类型的精度和特性可能因平台和实现而异。

对于时钟来说,最常用的成员函数应该是 now(),即获取当前的时钟时间。

对于 system_clock,还有两个较为常用的成员函数,为 from_time_tto_time_t,即通过 C 风格下的 time_t(即以秒计的时间戳),来获取该时间戳下的时间点 (time_point)。或将时间点转换为 C 风格下的时间戳。

时间点 (time_point)

时间点即为特定时钟下的某个时刻。也就是说,chrono 库中,如果要指定一个时间,需要确定三个元素。

  1. 是哪种时钟下的时间点
  2. 这种时钟下的纪元(即零点时间)是什么,(如我们熟知的 UTC 1970-01-01 00:00:00
  3. 该时间点距离纪元的时间间隔是多久。

时间间隔 (duration)

时间间隔即两个时间点相距多久。chrono 库中,要表示时间间隔,要确定以下两点:

  • Rep:数据表示方式(存储类型),如 intfloatuint64_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 中,就有了 secondsminuteshours 等类型的时间时间,但是对于天、月、年这个级别的时间间隔的支持,拖延到了 C++20 才进入标准。

chronoliterals 支持上,C++14 中我们可以方便地使用 60s30min12h 来表示时间间隔,但却不支持天、月、年。C++ 20 中加入了 d, y,来表示天和年,但注意,这是日期上的日和年,而不是时间间隔上的!(如 23d 表示某一个月的 23 号,而非时间长度上的 23 天)年可以理解,毕竟不同年份的时间不一致,但没有加入天数上的字面词,在代码风格上不能与秒、分钟达成统一,(如,我表达 3 秒可以写作 3s,表达 10 天却要写成 days {10})我感觉还是有些不完美、略有遗憾吧。

我们可以通过 24h * 10 来表达 10 天,却不能够使用 (10 * 24)h,我觉得也是在语言设计上的一点缺陷,后者这明明是 constexpr 的,也有些遗憾吧。

感悟

我之所以写这篇学习笔记,是因为在新的工作中,我需要记录时间,用于计算特定时间内处理了多少条数据等,并打印出日志来进行方案评估。

我自诩为看过一些 C++ 书的,了解一些 C++,却从未正式地写过 C++。于是知道有这种 API,却没有真实地使用过这个 API,没有实践的经历及由此而来的肌肉记忆。因此在写代码时,不能够很流畅地写出,感觉很生涩。真实地感受到了 “纸上得来终觉浅,绝知此事要躬行。”

我们总说,编程语言没那么重要,数据结构、算法、系统架构这些才是重要的。这句话我是认可的,但令一方面,我觉得对程序员来说,编码是我们表达我们思想的方式。语言决定者对语言底层思想的设计,我们对该语言的理解、应用能力,(一定程度上)决定了我们在写代码时表达我们思想的能力。

写这篇笔记时,刚好是小满(虽然写完不是了)。我很喜欢这个节气,万物至此,小得圆满,很有中国传统哲学的智慧。