文章目录
- chrono简介
- std::chrono::duration
- duratio基本介绍
- 基本概念
- 使用引入
- std::ratio 参数深入
- 特化的duratio
- 改造之前的代码
- 静态成员函数 count
- 原型
- 例子
- 构造函数
- 支持加减乘除运算
- 编译细节
- 支持比较运算符
- 查询范围
- 类型转换
- 例子引入
- 修改seconds的范围
- 浮点类型
- 系统特化的duratio
- 自定义单位转换
- duratio源码补充
- radio 源码补充
- duration_cast()分析
- 例子(重要)
- 预定义的 radio
- 改写例子中的代码
- 改写例子中的代码
chrono简介
chrono
是一个基于模板的,面向对象的,设计优雅且功能强大的time library。chrono
内部定义了三种和时间相关的类型:
- duration:一个duration就代表了一个时间段,比如2分钟,4小时等等。
- clock: clock的作用就相当于我们日常使用的手表:显示时间。
chrono
内部定义了三种clock:system clock
、steady clock
和high-resolution-clock
。 - time point:time point表示某个特定的时间点。
std::chrono::duration
duratio基本介绍
基本概念
template<
class Rep,
class Period = std::ratio<1>
> class duration;
类模板 std::chrono::duration
表示时间间隔。
它由 Rep
类型的计次数和计次周期组成,其中计次周期是一个编译期有理数常量,表示从一个计次到下一个的秒数。
存储于 duration
的数据仅有 Rep
类型的计次数。若 Rep
是浮点数,则 duration
能表示小数的计次数。 Period
被包含为时长类型的一部分,且只在不同时长间转换时使用。
使用引入
例子:用 chorono 库 刻画 5s 的时间间隔
std::chrono::duration<float, std::ratio<2 / 1>> Five_Second = std::chrono::duration<float, ratio<2 / 1>>(2.5);
这里的Rep (计次数类型)
就是float
, 这里的计次数
就是 2.5
, 这里的 计次周期
就是 2/1 =2 s
时间间隔 = 计次数(2.5)
* 计次周期(2)
=5s
- 它所表示的时间间隔和下面是等价的
std::chrono::duration<int, std::ratio<5 / 1>> Five_Second = std::chrono::duration<int, ratio<5 / 1>>(1);
这里的Rep (计次数类型)
就是int
, 这里的计次数
就是 1
, 这里的 计次周期
就是 5/1 =5 s
时间间隔 = 计次数(1)
* 计次周期(5)
=5s
std::ratio 参数深入
duration
的声明包含两个模板参数,第一个模板参数是C++的原生数值类型,如long
, long long
等,代表了duration
的数值部分。第二个模板参数_Period
又是一个模板类std::ratio
,它的定义如下:
template<
std::intmax_t Num,
std::intmax_t Denom = 1
> class ratio;
// file: ratio
namespace chrono {
// ratio以模板的方式定义了有理数,比如ratio<1,60>就表示有理数 ‘1/60’
// _Num代表 'numerator'(分子)
// _Den代表 'denominator'(分母)
template<intmax_t _Num, intmax_t _Den = 1>
class ration {
// 求__Num的绝对值
static constexpr const intmax_t __na = __static_abs<_Num>::value;
// 求_Den的绝对值
static constexpr const intmax_t __da = __static_abs<_Den>::value;
// __static_sign的作用是求符号运算
static constexpr const intmax_t __s = __static_sign<_Num>::value * __static_sign<_Den>::value;
// 求分子、分母的最大公约数
static constexpr const intmax_t __gcd = __static_gcd<__na, __da>::value;
public:
// num是化简后的_Num
static constexpr const intmax_t num = __s * __na / __gcd;
// den是化简后的_Den
static constexpr const intmax_t den = __da / __gcd;
};
}
ratio
用两个整数型模板参数来表示一个有理数的分子和分母部分,比如ratio<1, 1000>
就表示有理数0.001
。理解了这一点,我们再来看duration
的定义:
template<class _Rep, class _Period = ratio<1> > class duration
ratio
在这里的确切含义为:以秒为单位的放大倍率,比如ratio<60, 1>
表示一个1秒的60倍,也就是1分钟
,而ratio<1, 1000>
表示1秒的千分之一倍,也就是1毫秒
。所以duration<long, ratio<60, 1>>
就定义了一个类型为long
的duration
,而这个duration
的单位为“分钟”。
特化的duratio
-
chrono中宏定义了许多特例化了的duration:
就是常见的hours,miniutes,seconds,milliseconds等,使用std::chrono::milliseconds直接使用
namespace chrono {
// 1nano = 1/1,000,000,000 秒
typedef ratio<1LL, 1000000000LL> nano;
// 1micro = 1/1,000,000秒
typedef ratio<1LL, 1000000LL> micro;
// 1milli = 1/1,000秒
typedef ratio<1LL, 1000LL> milli;
// 1centi = 1/100秒
typedef ratio<1LL, 100LL> centi;
// 1kilo = 1,000秒
typedef ratio<1000LL, 1LL> kilo;
// 1mega = 1,000,000秒
typedef ratio<1000000LL, 1LL> mega;
// ...
typedef duration<long long, nano> nanoseconds; // nanosecond是duration对象 ,nano 是 ratio对象
typedef duration<long long, micro> microseconds;
typedef duration<long long, milli> milliseconds;
typedef duration<long long > seconds;
typedef duration< long, ratio< 60> > minutes;
typedef duration< long, ratio<3600> > hours;
// ...
}
改造之前的代码
例子:用 chorono 库 刻画 5s 的时间间隔
std::chrono::seconds Five_Second = std::chrono::seconds(5); // 这里的seconds 就是 特化的duration
静态成员函数 count
原型
constexpr rep count() const;
std::chrono::duration<Rep,Period>::count
- 返回值
此 duration 的计次数。
例子
#include <iostream>
#include <chrono>
#include <thread>
using namespace std;
int main(int argc, char **argv)
{
std::chrono::seconds Five_Second = std::chrono::seconds(5);
cout << "Five_seconds的计次数为:: " << Five_Second.count() << endl;
}
Five_seconds的计次数为:: 5
构造函数
void func(std::chrono::seconds d)
{
cout << "d的计次数为:: " << d.count() << endl;
}
int main(int argc, char **argv)
{
// std::chrono::seconds Five_Second = std::chrono::seconds(5);
// cout << "Five_seconds的计次数为:: " << Five_Second.count() << endl;
// todo1 构造函数
std::chrono::seconds Five_Second1; // 未初始化
std::chrono::seconds Five_Second2{}; // 零初始化
std::chrono::seconds Five_Second3{5}; // ok 5s
std::chrono::seconds Five_Second3(5s); // ok 5s
// todo2 不允许隐式类型转换
// std::chrono::seconds Five_Second3 = 5; // error 不允许隐式类型转换
// func(5); // error 不允许隐式类型转换
func(5s); // ok 5s
}
支持加减乘除运算
void func(std::chrono::seconds d)
{
cout << "d的计次数为:: " << d.count() << endl;
}
void func2()
{
auto x = 3s;
x += 2s;
func(x);
x = x - 5s;
// x+=5;//error 不能加 int
func(x);
}
// d的计次数为::5 d的计次数为::0
编译细节
比较编译器所花费时间
- code1
std::chrono::seconds func(std::chrono::seconds d1,std::chrono::seconds d2)
{
return d1+d2;
}
- code 2
int64_t func(int64_t x1,int64_t x2)
{
return x1+x2;
}
-
实际上他们的汇编代码是相同的,除了顶部名称修改
-
不仅仅局限在此,下面代码的运算也是相同的
支持比较运算符
namespace detail2
{
constexpr auto time_limit = 5s;
void fun(std::chrono::seconds s)
{
if (s == time_limit)
{
cout << "equal time" << endl;
}
else if (s <= time_limit)
{
cout << "in time" << endl;
}
else
{
cout << "out of time" << endl;
}
}
}
detail2::fun(1s);
detail2::fun(5s);
detail2::fun(6s);
in time
equal time
out of time
查询范围
auto max = std::chrono::seconds::max();
auto min = std::chrono::seconds::min();
cout << "max = " << max.count() << endl;
cout << "min = " << max.count() << endl;
类型转换
例子引入
- 一般来说: 如果一个 < chrono > 转换是无损的,那么它是隐式的。如果一个转换不是无损的,它不会在没有特殊语法的情况下编译。
- 如果转换会带来精度损失,编译就会报错。如果一定需要这样的转换,就要进行explicitly(明确的)的转换
namespace detail
{
void func()
{
auto time_day = 24h;
auto time_seconds = std::chrono::seconds(time_day);
cout << time_seconds.count() << endl;
}
// void func2()
// {
// auto time_seconds = 86400s;
// auto time_day = std::chrono::hours(time_seconds);
// // chrono 库不支持 将 duration(持续时间)类型从更精确的类型转换为不太精确的类型
// cout << time_day.count() << endl;
// }
void func3()
{
auto time_seconds = 86400s;
auto time_day = std::chrono::duration_cast<std::chrono::hours>(time_seconds);
cout << time_day.count() << endl;
}
void func4()
{
auto mi = std::chrono::milliseconds{3400ms};
std::chrono::seconds s = std::chrono::duration_cast<std::chrono::seconds>(mi);
cout << s.count() << endl;
}
}
int main(int argc, char **argv)
{
// 初步认识 duration_cast()强制转换
detail::func(); // 这里没有损失精度
// detail::func2(); // error
detail::func3(); // 这里没有损失精度+
detail::func4(); //输出 3 s ,损失精度
}
修改seconds的范围
- 如果觉得64bit表示seconds太浪费,
<chrono>
还提供下面的方法,依然可以像上面的那些duration那样互转。
using seconds32 = std::chrono::duration<int32_t>;
- 甚至下面这个也能工作:
using seconds32 = std::chrono::duration<uint32_t>;
- 甚至下面这个也能工作(使用“safeint”库):
using seconds32 = std::chrono::duration<safe<uint32_t>>;
- 甚至下面这个也能工作(使用浮点类型):
using fseconds = std::chrono::duration<float>;
浮点类型
对于浮点表示形式,可以从任何精度进行隐式转换,而不需要使用 period _ cast。其基本原理是没有截尾误差(只有舍入误差)。所以隐式转换是安全的。
原始的毫秒
typedef ratio<1LL, 1000LL> milli;
using my_ms = std::chrono::duration<double, std::milli>; // double 也可以用float代替
void myf(my_ms d)
{
cout << "my_ms:: " << d.count() << " ms\n";
};
void f(std::chrono::milliseconds d)
{
cout << "f::" << d.count() << " ms\n";
};
void func()
{
// f(45ms + 63us);//原始的毫秒不支持隐式类型转换
myf(45ms + 63us); // 45.063 ms
}
系统特化的duratio
typedef ratio<1LL, 1000000000LL> nano;
// 1micro = 1/1,000,000秒
typedef ratio<1LL, 1000000LL> micro;
// 1milli = 1/1,000秒
typedef ratio<1LL, 1000LL> milli;
// 1centi = 1/100秒
typedef ratio<1LL, 100LL> centi;
// 1kilo = 1,000秒
typedef ratio<1000LL, 1LL> kilo;
// 1mega = 1,000,000秒
typedef ratio<1000000LL, 1LL> mega;
typedef duration<long long, nano> nanoseconds; // 纳秒
typedef duration<long long, micro> microseconds; // 微秒
typedef duration<long long, milli> milliseconds; // 毫秒
typedef duration<long long> seconds; // 秒
typedef duration<int, ratio<60> > minutes; // 分钟
typedef duration<int, ratio<3600> > hours; // 小时
自定义单位转换
#include <iostream>
#include <chrono>
typedef std::chrono::duration<float, std::ratio<3, 1> > three_seconds;
typedef std::chrono::duration<float, std::ratio<1, 10> > one_tenth_seconds;
int main()
{
three_seconds s = std::chrono::duration_cast<three_seconds>(one_tenth_seconds(3));
std::cout << "3 [1/10 seconds] equal to " << s.count() << " [3 seconds]\n";
std::cin.get();
}
duratio源码补充
std::chrono::duration是一个模板类,关键代码摘录如下(格式有调整):
template<class _Rep, class _Period>
class duration {
public:
typedef duration<_Rep, _Period> _Myt;
typedef _Rep rep;
typedef _Period period;
// constructor, save param to _MyRep, used by count() member function.
template<class _Rep2,
class = typename enable_if<is_convertible<_Rep2, _Rep>::value
&& (treat_as_floating_point<_Rep>::value || !treat_as_floating_point<_Rep2>::value),
void>::type>
constexpr explicit duration(const _Rep2& _Val)
: _MyRep(static_cast<_Rep>(_Val))
{
}
constexpr _Rep count() const { return (_MyRep); }
};
// convert duration from one unit to another.
template<class _To, class _Rep, class _Period> inline
constexpr typename enable_if<_Is_duration<_To>::value, _To>::type
duration_cast(const duration<_Rep, _Period>& _Dur)
{
typedef ratio_divide<_Period, typename _To::period> _CF;
typedef typename _To::rep _ToRep;
typedef typename common_type<_ToRep, _Rep, intmax_t>::type _CR;
#pragma warning(push)
#pragma warning(disable: 6326) // Potential comparison of a constant with another constant.
return (_CF::num == 1 && _CF::den == 1
? static_cast<_To>(static_cast<_ToRep>(_Dur.count()))
: _CF::num != 1 && _CF::den == 1
? static_cast<_To>(static_cast<_ToRep>(
static_cast<_CR>(
_Dur.count()) * static_cast<_CR>(_CF::num)))
: _CF::num == 1 && _CF::den != 1
? static_cast<_To>(static_cast<_ToRep>(
static_cast<_CR>(_Dur.count())
/ static_cast<_CR>(_CF::den)))
: static_cast<_To>(static_cast<_ToRep>(
static_cast<_CR>(_Dur.count()) * static_cast<_CR>(_CF::num)
/ static_cast<_CR>(_CF::den))));
#pragma warning(pop)
}
radio 源码补充
std::ratio是一个模板类,关键代码摘录如下(格式有调整):
template<intmax_t _Nx, intmax_t _Dx = 1>
struct ratio
{
static_assert(_Dx != 0, "zero denominator");
static_assert(-INTMAX_MAX <= _Nx, "numerator too negative");
static_assert(-INTMAX_MAX <= _Dx, "denominator too negative");
static constexpr intmax_t num = _Sign_of<_Nx>::value
* _Sign_of<_Dx>::value * _Abs<_Nx>::value / _Gcd<_Nx, _Dx>::value;
static constexpr intmax_t den = _Abs<_Dx>::value / _Gcd<_Nx, _Dx>::value;
typedef ratio<num, den> type;
};
第一个参数_Nx
代表了分子,第二个参数 _Dx
代表了分母。
num
是计算后的分子,den
是计算后的分母。在duration转换的时候会用到这两个值。
注:这里的计算是指约分,可以看到传入的分子和分母都除以了最大公约数。
num
是numerator
的缩写,表示分子。
den
是denominator
的缩写,表示分母。
duration_cast()分析
- 注明:这个函数是在duration 源码中的
函数duration_cast()
提供了在不同的时间单位之间进行转换的功能。
duration_cast()
主要分为两部分:
- 通过
ratio_divide
定义了从一个ratio转换到另外一个ratio的转换比例。
比如1/10
到2/5
的转换比例是1/4
((1/10/(2/5)) = 1/4),也就是说一个1/10
相当于1/4
个2/5
。
对应到代码里就是_CF::num = 1, _CF::den = 4
. - 根据转换比例把n个单位的原数据转换到目标数据(return语句)
return
语句写的这么复杂是为了效率,避免不必要的乘除法,当分子是1的时候没必要乘,当分母是1的时候没必要除。
简化一下(去掉了强制类型转换)就是:
return _Dur.count() * (_CF::num / _CF::den);
通俗点讲:如果A
到B
的转换比例是num/den
,那么1
个A
可以转换为num/den
个B
, n
个A
可以转换为 n * (num/den)
个B
。
例子(重要)
#include <iostream>
#include <chrono>
int main()
{
std::chrono::milliseconds mscond(1000); // 1 second
std::cout << mscond.count() << " milliseconds.\n"; //1000
// 时间间隔 = `计次数(count)` * `计次周期(ration)`
std::cout << mscond.count() * std::chrono::milliseconds::period::num / std::chrono::milliseconds::period::den; // 1000* 1/1000
std::cout << " seconds.\n";
system("pause");
return 0;
}
这里的需要注意的是 std::chrono::milliseconds::period::num
和 std::chrono::milliseconds::period::den
拆开来理解:
std::chrono::milliseconds
是 duration 模板类的特化,也就是 duration类
, 在duration 类 的成员中有如下成员:
typedef duration<_Rep, _Period> _Myt;
typedef _Rep rep;
typedef _Period period;
所以 std::chrono::milliseconds::period
就是 引用duration中的 period成员, 接着看下面duration的类模版声明
template<
class Rep,
class Period = std::ratio<1>
> class duration;
其中 period 是 _Period 类型的 ,也就是 ratio<> 类型的;所以 period 就相当于 是 ratio 类型对象 ,再结合一下ratio源码
中存在两个成员 num 和 den .
就不难得出std::chrono::milliseconds::period::num
/ std::chrono::milliseconds::period::den
是 milliseconds (duration)的 ratio
(计数周期) 也就是 1/1000 【关键】
注:ratio 源码(部分)
static constexpr intmax_t num = _Sign_of<_Nx>::value
* _Sign_of<_Dx>::value * _Abs<_Nx>::value / _Gcd<_Nx, _Dx>::value;
static constexpr intmax_t den = _Abs<_Dx>::value / _Gcd<_Nx, _Dx>::value;
typedef ratio<num, den> type;
预定义的 radio
为了方便代码的书写,标准库提供了如下定义:
Type | Definition |
---|---|
yocto | std::ratio<1, 1000000000000000000000000>, if std::intmax_t can represent the denominator |
zepto | std::ratio<1, 1000000000000000000000>, if std::intmax_t can represent the denominator |
atto | std::ratio<1, 1000000000000000000> |
femto | std::ratio<1, 1000000000000000> |
pico | std::ratio<1, 1000000000000> |
nano | std::ratio<1, 1000000000> |
micro | std::ratio<1, 1000000> |
milli | std::ratio<1, 1000> |
centi | std::ratio<1, 100> |
deci | std::ratio<1, 10> |
deca | std::ratio<10, 1> |
hecto | std::ratio<100, 1> |
kilo | std::ratio<1000, 1> |
mega | std::ratio<1000000, 1> |
giga | std::ratio<1000000000, 1> |
tera | std::ratio<1000000000000, 1> |
peta | std::ratio<1000000000000000, 1> |
exa | std::ratio<1000000000000000000, 1> |
zetta | std::ratio<1000000000000000000000, 1>, if std::intmax_t can represent the numerator |
yotta | std::ratio<1000000000000000000000000, 1>, if std::intmax_t can represent the numerator |
改写例子中的代码
结合预定义的radio
#include <iostream>
#include <chrono>
using namespace std;
int main()
{
std::chrono::milliseconds mscond(1000); // 1000ms
std::cout << mscond.count() << " milliseconds.\n"; // 1000
// 时间间隔 = `计次数(count)` * `计次周期(ration)`
std::milli mi;
std::cout << mscond.count() * mi.num / mi.den; // 1000* 1/1000
// cout << "mi.num" << mi.num << "mi.den" << mi.den << endl;
std::cout
<< " seconds.\n";
return 0;
}
|
| zetta | std::ratio<1000000000000000000000, 1>, if std::intmax_t can represent the numerator |
| yotta | std::ratio<1000000000000000000000000, 1>, if std::intmax_t can represent the numerator |
改写例子中的代码
结合预定义的radio
#include <iostream>
#include <chrono>
using namespace std;
int main()
{
std::chrono::milliseconds mscond(1000); // 1000ms
std::cout << mscond.count() << " milliseconds.\n"; // 1000
// 时间间隔 = `计次数(count)` * `计次周期(ration)`
std::milli mi;
std::cout << mscond.count() * mi.num / mi.den; // 1000* 1/1000
// cout << "mi.num" << mi.num << "mi.den" << mi.den << endl;
std::cout
<< " seconds.\n";
return 0;
}