
std::ratio 是什么,它真能零开销?
它不是运行时工具,而是编译期有理数类型模板——std::ratio<N, D> 在实例化时就确定了值(比如 std::ratio<1, 1000> 表示毫秒/秒),所有运算(加减乘除、约分、gcd/lcm)都在模板推导阶段完成,生成的汇编里连一个指令都不会为它多出。
常见错误是把它当普通数值用:auto r = std::ratio{} * 1000; —— 错,std::ratio 没重载 * 运算符,也不能和整数直接运算;它只参与类型计算,不参与值计算。
- 必须配合
std::ratio_add、std::ratio_multiply等元函数操作类型 - 最终要靠
std::ratio<N,D>::num和std::ratio<N,D>::den取出编译期常量 - 所有输入必须是字面量整型常量(
constexpr),不能是变量或运行时读入的值
怎么把毫秒转纳秒?用 std::ratio_multiply 搭配 std::nano
标准库已定义好常用单位:比如 std::milli 是 std::ratio<1, 1000>,std::nano 是 std::ratio<1, 1000000000>。毫秒→纳秒本质是乘以 1000000,即 milli / nano 的比值。
正确写法是构造目标比例:从毫秒(ms)到纳秒(ns),需乘 std::ratio_multiply<std::milli, std::ratio<1, std::nano::den>>::type?太绕——更直接的是用除法语义:
立即学习“C++免费学习笔记(深入)”;
using ms_to_ns = std::ratio_divide<std::milli, std::nano>;
ms_to_ns::num 就是 1000000,ms_to_ns::den 是 1,表示“1 毫秒 = 1000000 纳秒”。
- 别手写
std::ratio<1000000, 1>:失去类型语义,且无法复用单位定义 -
std::ratio_divide<A,B>等价于 A/B,适合单位换算(如 ms / ns) - 结果类型自动约分,
std::ratio_divide<:kilo std::milli></:kilo>得到std::ratio<1000000, 1>,不是<1000,1/1000>
如何让自定义单位(比如“帧”)参与编译期时间换算?
假设一帧 = 16.666…ms(60fps),但 std::ratio 要求整数分子分母,所以不能用浮点。得选一个足够精确的近似分数,比如 std::ratio<1, 60> 秒/帧,再换算成纳秒/帧:
using frame_duration = std::ratio_multiply<std::ratio<1, 60>, std::nano>;
此时 frame_duration::num == 1000000000 / 60 == 16666666(向下取整),frame_duration::den == 1 —— 注意:整除截断是编译期行为,不会报错,但精度丢失不可逆。
- 误差来源只有初始分数近似,后续所有运算保持该精度,不会累积
- 若需更高精度,改用更大分母:如
std::ratio<1001, 60060>(≈1/60),但会增大编译负担,且num/den可能溢出intmax_t - 务必检查
frame_duration::num是否非零:除零在模板实例化时是 SFINAE 友好失败,但某些旧编译器可能静默截断
std::ratio 和 std::chrono::duration 混用时最易踩的坑
很多人以为 std::ratio 是 std::chrono::duration 的底层,就想手动拼 duration 类型。但直接写 std::chrono::duration<int, std::milli> 是对的,而写 std::chrono::duration<int, std::ratio_multiply<std::milli, std::ratio<2>>::type> 就容易翻车——因为 std::ratio_multiply 返回的是未命名类型,和标准单位类型不兼容,可能破坏 std::chrono 的特化逻辑。
- 优先用标准单位别名:
std::milli、std::micro、std::nano,而不是自己造std::ratio<1,1000000> - 需要组合时,先用元函数算出结果类型,再显式 alias:
using my_unit = std::ratio_multiply<:milli std::ratio<5>>::type;</:milli>,再传给duration - 跨 duration 转换(如
duration_cast)依赖std::common_type,若自定义 ratio 的num/den过大,可能导致common_type推导失败或溢出
真正关键的不是“能不能做”,而是“有没有必要绕开标准单位别名去手动 compose ratio”——多数时候,直接用 std::chrono::milliseconds 或 auto d = 5ms; 更安全,std::ratio 的价值在于构建新单位系统或做维度检查,不是替代 duration 的日常使用。









