c++ 时间库实现:duration 类的原理与复现解析
在现代编程中,时间处理是一个常见且重要的需求。c++11 标准库引入了 <chrono>
头文件,提供了一套处理时间的工具,其中 duration
类是时间表示的核心组件之一。在上一篇文章中,分析和复现了 <ratio>
, 本文将基于 ratio
深入解析 duration
类的实现原理,并详细介绍笔者自己复现这一功能强大的时间间隔表示类的完整过程。
duration 类的基本概念
duration
类模板用于表示时间间隔,它以模板参数的形式定义了两个关键要素:
rep_
:表示时间间隔的数值类型(如int64_t
)period_
:表示时间间隔的单位(通过ratio
模板定义)
这种设计使得 duration
能够灵活处理不同精度和单位的时间间隔,从纳秒到天数都能统一表示和操作。标准库中通过特化 ratio
模板定义了常见的时间单位:
- 纳秒:
nano = ratio<1, 1000000000>
- 微秒:
micro = ratio<1, 1000000>
- 毫秒:
milli = ratio<1, 1000>
- 秒:
ratio<1>
- 分钟:
ratio<60>
- 小时:
ratio<3600>
- 天:
ratio<86400>
duration 类的核心实现原理
1. 模板参数设计
duration
类的模板定义如下:
template<typename rep_, typename period_ = ratio<1>> class duration;
rep_
:存储时间间隔的具体数值,通常为整数类型(如int64_t
)period_
:时间单位,基于ratio
模板实现,默认单位为秒
采用参数化设计使得 duration
具有高度的灵活性和类型安全性,不同单位的 duration
是不同的类型,避免了隐式类型转换带来的错误。
2. 单位转换的核心:duration_cast
duration_cast
函数是实现不同时间单位转换的关键,其核心逻辑如下:
/// 用于将一个时间段从一个周期单位转换到另一个周期单位 template<typename toduration_, typename rep_, typename period_> constexpr toduration_ duration_cast(const duration<rep_, period_> &d__) { using cf = ratio_divide<period_, typename toduration_::period>; /// 类型转换(整型除法,可能发生截断) auto r_ = static_cast<typename toduration_::rep>( static_cast<long long>(d__.count()) * cf::num / cf::den ); return toduration_(r_); }
- 通过
ratio_divide
计算两个时间单位的转换因子(时间转换因子用于在不同时间单位或者系统之间进行转换,计算的原理和场景是强相关的) - 使用编译期计算的
cf::num
和cf::den
完成单位转换,分别代表源单位与目标单位的比例关系中的分子部分与分母部分 - 利用
static_cast
进行安全的类型转换
通过这种分子/分母的组合实现方式确保了单位转换在编译期完成,既保证了运行时的效率,同时又避免了浮点数的误差,实现了类型安全并且高精度的时间单位转换。
3. 数值表示与存储
duration
类内部使用 rep_
类型存储时间间隔的数值:
private: rep_ rep_;
通过公开的 count()
方法获取存储的数值:
constexpr rep_ count() const { return rep_; }
4. 构造函数设计
duration
提供了多种构造方式以满足不同需求:
/// 默认构造,数值为0 constexpr duration() : rep_() {} /// 从数值构造 template<typename rep2_> explicit constexpr duration(const rep2_ &r_) : rep_(r_) {} /// 从其他duration构造 template<typename rep2_, typename period2_> constexpr duration(const duration<rep2_, period2_> &d_) : rep_(duration_cast<duration>(d_).count()) {}
运算符重载与算术操作
duration
类实现了完整的算术运算符重载,使得时间间隔的计算变得直观自然。核心的思想都是将其转换为相同单位后再进行运算。
1. 一元运算符
/// 正号 constexpr duration operator+() const { return *this; } /// 负号 constexpr duration operator-() const { return duration(-count()); }
2. 递增递减运算符
/// 前置递增 duration &operator++() { ++rep_; return *this; } /// 后置递增 duration operator++(int) { duration temp_(*this); ++*this; return temp_; } /// 递减运算符类似,不重复贴出实现源码
3. 复合赋值运算符
实现复合运算符进行的方式都是类似的,这里给出+=的实现方式,其他运算符类似。
duration &operator+=(const duration &d_) { rep_ += d_.count(); return *this; }
4. 二元运算符实现
二元运算符的实现采用了"统一单位后计算"的策略:
template<typename rep1_, typename period1_, typename rep2_, typename period2_> constexpr auto operator+(const mychrono::duration<rep1_, period1_> &lhs, const mychrono::duration<rep2_, period2_> &rhs) { /// 找出更高精度的时间单位 using commonperiod = typename mychrono::higher_precision_duration<rep1_, period1_, rep2_, period2_>::type::period; /// 确定统一的数值类型 using commonrep = typename std::common_type<rep1_, rep2_>::type; using commonduration = mychrono::duration<commonrep, commonperiod>; /// 转换到统一类型后计算 commonrep lhs_val = duration_cast<commonduration>(lhs).count(); commonrep rhs_val = duration_cast<commonduration>(rhs).count(); return commonduration(lhs_val + rhs_val); }
关键步骤包括:
- 确定更高精度的时间单位(周期更小的单位)
- 确定统一的数值类型(使用
std::common_type
) - 将两个操作数转换为统一类型
- 执行算术运算并返回结果
精度选择与类型推导
1. 更高精度类型的选择
higher_precision_duration
结构体用于在两个 duration
类型中选择精度更高的类型:
template<typename rep1_, typename period1_, typename rep2_, typename period2_> struct higher_precision_duration { using type = typename std::conditional< ratio_less<period1_, period2_>::value, duration<rep1_, period1_>, duration<rep2_, period2_>>::type; };
- 通过
ratio_less
比较两个时间单位的周期 - 选择周期更小(精度更高)的
duration
类型作为结果
2. 数值类型的安全处理
在进行算术运算时,使用 std::common_type
确定安全的数值类型,定义与头文件如下:
#include <type_traits> template< class... t > struct common_type; template< class... t > using common_type_t = typename common_type<t...>::type; // c++14起的别名模板
实现的功能是:同时给出多个类型 t1、t2…,tn,std::common_type
会推导出一个公共类型,使得所有的ti
类都可以隐式转换到公共类型,同时是可以同时满足所有类型的最小公共类型。
所以,在复现过程中使用如下方式,找到公共类型:
using commonrep = typename std::common_type<rep1_, rep2_>::type;
这种方式确保了不同数值类型(如 int
和 long long
)之间的运算不会发生精度丢失。
预定义的时间单位
为了方便使用,代码中预定义了常见的时间单位特化:
using nano = ratio<1, 1000000000>; using micro = ratio<1, 1000000>; using milli = ratio<1, 1000>; using nanoseconds = duration<int64_t, nano>; using microseconds = duration<int64_t, micro>; using milliseconds = duration<int64_t, milli>; using seconds = duration<int64_t, ratio<1>>; using minutes = duration<int64_t, ratio<60>>; using hours = duration<int64_t, ratio<3600>>; using days = duration<int64_t, ratio<86400>>;
这些定义使得我们可以用有意义的类型名表示不同精度的时间间隔,例如:
milliseconds ms(1000); /// 1000毫秒 seconds s(1); /// 1秒 hours h(24); /// 24小时
使用示例
下面给出关于 duration
类的使用方法:
1. 基本时间间隔创建
/// 创建不同单位的时间间隔 milliseconds ms(500); /// 500毫秒 seconds s(1); /// 1秒 minutes m(10); /// 10分钟 /// 从其他duration构造 seconds s2(ms); /// 500毫秒转换为0秒(发生截断) milliseconds ms2(s); /// 1秒转换为1000毫秒
2. 时间间隔计算
seconds s1(5); seconds s2(3); /// 加法运算 seconds s_sum = s1 + s2; /// 8秒 /// 减法运算 seconds s_diff = s1 - s2; /// 2秒 /// 复合赋值 s1 += s2; /// s1现在为8秒 /// 不同单位的运算 milliseconds ms(1500); seconds s_total = s1 + ms; /// 自动转换为秒后相加,结果为9.5秒(假设使用浮点类型)
3. 单位转换
seconds s(1); /// 转换为毫秒 milliseconds ms = duration_cast<milliseconds>(s); /// 1000毫秒 /// 转换为纳秒 nanoseconds ns = duration_cast<nanoseconds>(s); /// 1000000000纳秒 /// 精度损失示例 milliseconds ms(500); seconds s = duration_cast<seconds>(ms); /// 0秒
设计亮点与技术总结
1. 编译期计算优化
通过使用 ratio
模板中的(ratio_divide
、ratio_less
等方法),许多计算可以在编译期完成,在编译期计算可以:
- 提高运行时效率,避免重复计算
- 提前发现潜在的单位转换错误
- 支持编译期常量表达式(
constexpr
)
2. 类型安全设计
- 不同单位的
duration
是不同的类型,避免了隐式转换错误 - 通过
explicit
构造函数防止意外类型转换 - 精确的模板类型推导确保了运算的安全性
3. 扩展性设计
- 基于模板的设计使得可以轻松支持自定义时间单位
- 统一的接口设计使得新的时间单位可以无缝融入现有框架
- 通过
duration_cast
实现任意单位之间的转换
与标准库 <chrono>
的对比
本文复现的 duration
类实现了标准库 <chrono>
中 duration
类的核心功能,但也存在一些差异:
- 命名空间:复现版本使用
mychrono
命名空间,这里是我基于std::chrono
实现的我自己的mychrono
类 - 完整度:标准库实现更为完整,包含更多的特化和辅助功能,我以学习和理解原理为主,故只完成部分内容
- 错误处理:标准库包含更完善的错误处理机制
- 浮点支持:复现版本主要使用整数类型,标准库同时支持浮点类型
总结
duration
类是 c++ 时间处理的基础组件,其设计思想体现了现代 c++ 模板编程的强大实力。通过将时间单位和数值表示分离,duration
实现了灵活而类型安全的时间间隔表示和计算。完成对duration
的理解学习之后,对模板元编程方法理解更深刻。
最后,本文仅仅是我在学习复习相关知识点的时候进行的自我总结和整理,存在很多不好的地方,如果错误请指出,接收一切批评并加以改正,认真学技术,加油。如有侵权,请联系我删除~
到此这篇关于c++ 时间库实现:duration 类的原理与复现解析的文章就介绍到这了,更多相关c++ 时间库duration 类内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!