C++ / a working model

82 / 103   ·   C++11   ·   约 12 分钟

chrono 单位:别把 1500 毫秒变成 1 毫秒

先记住这句话

时间类型把数值和单位绑定在一起,count() 却只返回裸计数。通过毫秒预算和秒级接口的转换,观察向零截断、精度损失及显式单位边界;不依赖真实时钟或休眠,因此结果可重复。

本篇内容
  1. 单位是类型的一部分
  2. 显式转换不等于没有信息损失
  3. 超时预算与日历日期分开
  4. 运行示例
  5. 动手练习
READING EVIDENCE / 已读完整正文

The C++ Standard Library: A Tutorial and Reference

已逐段通读英文第2版第1—19章全部正文(pp.1—1030),参考文献及补充章S.1—S.3(pp.1103—1161);主文本L635—44987、补充正文L48029—50769已读。随后直接核读51个图题所在PDF页和6个概率公式/上下文页,包含所有已定位图示及§17.1.5二维公式。full指正文与这些实质图示读完,不表示逐页校勘全部PDF;索引仅导航抽查,未逐条读。

查看版本、实际阅读范围与原文入口 →

单位是类型的一部分

milliseconds{1500}seconds{1500} 的计数相同,含义却相差一千倍。duration<Rep, Period> 用表示类型和编译期比例记录这层意义;加减不同单位时,库选择共同表示,而不是让调用者手工补乘一千。

危险通常发生在调用 count() 以后。把毫秒的 count 直接放进秒类型,是重新解释单位,不是换算。接口优先接收 duration,确实要对接只收整数的系统 API 时,再在边界执行转换并标明单位。

显式转换不等于没有信息损失

示例把 1500 毫秒转为整数秒,得到 1 秒,余量为 500 毫秒;负的 1500 毫秒转为整数秒是 -1,不是 -2。整数 duration_cast 按整数运算向零截断,不能当作数学向下取整。

我们把原值减去转换结果,明确检查被丢弃的部分。这比只断言转换没有崩溃更有意义。编译期 ratio 能检查比例的某些错误,却不会替所有运行时计数保证不溢出;真实输入仍要限制可表示范围。

超时预算与日历日期分开

超时要求的是经历多久,不是墙上时钟显示什么。书中区分 duration、time_point 和 clock,对今天仍是有用的建模方法;衡量间隔通常选择单调的 steady_clock,而不依赖可能被校时的系统时间。

本例不读取 now,也不 sleep,只研究可复现的单位契约。C++20 已扩充日历和时区设施;不要照搬早期用固定小时数冒充月份或年份的办法。需要按天、月计算日期时,应先定义日历语义,而不是把它伪装成秒数。

容易答错的地方

  • duration_cast 到整数粗单位会截断,不会自动四舍五入;负数也不是向下取整。
  • ratio 的编译期检查不保护所有运行时 count 运算;极大持续时间换单位仍可能越界。

运行一个例子

最低标准 C++11 · 完整程序 · 下载 .cpp

#include <cassert>
#include <chrono>
#include <ratio>
int main() {
    using namespace std::chrono;
    const milliseconds budget(1500);
    const seconds whole = duration_cast<seconds>(budget);
    const milliseconds remainder = budget - whole;
    assert(whole.count() == 1);
    assert(remainder.count() == 500);
    assert(duration_cast<seconds>(milliseconds(-1500)).count() == -1);
    const seconds wrong(budget.count());
    assert(wrong != budget);
    const duration<double> precise = budget;
    assert(precise.count() == 1.5);
    using Tick = duration<int, std::ratio<1, 4>>;
    assert(duration_cast<milliseconds>(Tick(3)).count() == 750);
    assert(whole + remainder == budget);
}

在本地编译

g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread books-cpp-standard-library.cpp -o example && ./example

预期结果

预期:正常退出、无输出;所有 assert 通过。

CHECK YOUR UNDERSTANDING

合上答案,试着解释。

只对 0—10000 毫秒的非负预算,写出“至少覆盖预算”的整秒转换。

查看参考答案

先验证范围,再令 auto seconds = std::chrono::seconds((budget.count() + 999) / 1000);。0 得 0,1 得 1,1000 得 1,1500 得 2。已限制上界,所以加 999 安全;若不限定范围,应改成商加非零余数,且检查目标表示范围。C++17 起也可用 std::chrono::ceil<std::chrono::seconds>(budget)

继续查证

标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。

回到目录