chrono
是一个基于模板的,面向对象的,设计优雅且功能强大的time library。chrono
内部定义了三种和时间相关的类型:
chrono
内部定义了三种clock:system clock
、steady clock
和high-resolution-clock
。template<
class Rep,
class Period = std::ratio<1>
> class duration;
类模板 std::chrono::duration
表示时间间隔。
它由 Rep
类型的计次数和计次周期组成,其中计次周期是一个编译期有理数常量,表示从一个计次到下一个的秒数。
存储于 duration
的数据仅有 Rep
类型的计次数。若 Rep
是浮点数,则 duration
能表示小数的计次数。 Period
被包含为时长类型的一部分,且只在不同时长间转换时使用。
例子:用 chorono 库 刻画 5s 的时间间隔
std::chrono::duration> Five_Second = std::chrono::duration>(2.5);
这里的Rep (计次数类型)
就是float
, 这里的计次数
就是 2.5
, 这里的 计次周期
就是 2/1 =2 s
时间间隔 = 计次数(2.5)
* 计次周期(2)
=5s
std::chrono::duration> Five_Second = std::chrono::duration>(1);
这里的Rep (计次数类型)
就是int
, 这里的计次数
就是 1
, 这里的 计次周期
就是 5/1 =5 s
时间间隔 = 计次数(1)
* 计次周期(5)
=5s
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
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
的duration
,而这个duration
的单位为“分钟”。
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 nanoseconds; // nanosecond是duration对象 ,nano 是 ratio对象
typedef duration microseconds;
typedef duration milliseconds;
typedef duration 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
constexpr rep count() const;
std::chrono::duration
此 duration 的计次数。
#include
#include
#include
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
比较编译器所花费时间
std::chrono::seconds func(std::chrono::seconds d1,std::chrono::seconds d2)
{
return d1+d2;
}
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;
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(time_seconds);
cout << time_day.count() << endl;
}
void func4()
{
auto mi = std::chrono::milliseconds{3400ms};
std::chrono::seconds s = std::chrono::duration_cast(mi);
cout << s.count() << endl;
}
}
int main(int argc, char **argv)
{
// 初步认识 duration_cast()强制转换
detail::func(); // 这里没有损失精度
// detail::func2(); // error
detail::func3(); // 这里没有损失精度+
detail::func4(); //输出 3 s ,损失精度
}
还提供下面的方法,依然可以像上面的那些duration那样互转。using seconds32 = std::chrono::duration;
using seconds32 = std::chrono::duration;
using seconds32 = std::chrono::duration>;
using fseconds = std::chrono::duration;
对于浮点表示形式,可以从任何精度进行隐式转换,而不需要使用 period _ cast。其基本原理是没有截尾误差(只有舍入误差)。所以隐式转换是安全的。
原始的毫秒
typedef ratio<1LL, 1000LL> milli;
using my_ms = std::chrono::duration; // 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
}
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 nanoseconds; // 纳秒
typedef duration microseconds; // 微秒
typedef duration milliseconds; // 毫秒
typedef duration seconds; // 秒
typedef duration > minutes; // 分钟
typedef duration > hours; // 小时
#include
#include
typedef std::chrono::duration > three_seconds;
typedef std::chrono::duration > one_tenth_seconds;
int main()
{
three_seconds s = std::chrono::duration_cast(one_tenth_seconds(3));
std::cout << "3 [1/10 seconds] equal to " << s.count() << " [3 seconds]\n";
std::cin.get();
}
浮点类型
namespace detail2
{
int work()
{
int sum = 0;
for (int i = 0; i < 1e8; ++i)
sum += i * i;
return sum;
}
void func()
{
auto start = std::chrono::steady_clock::now();
volatile int result = work();
auto finish = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(finish - start);
std::cout << duration.count() << "ms" << std::endl;
}
/*
todo 但是这样只能输出整数毫秒,如果想要更精确一点,一种方法是转换成microseconds以后除以1000.0,
todo 更优雅地可以自己定义一种时间段类型,如duration,其中double表示这种时间段类型用double来存储时钟周期数量,milli表示时钟周期为1ms。
todo 从由整数表示的duration到由浮点数表示的duration的转换可以由duration的构造函数来完成,无需再用duration_cast:
*/
// 自定义时间段类型,提高精度
void func2()
{
auto start = std::chrono::steady_clock::now();
volatile int result = work();
auto finish = std::chrono::steady_clock::now();
using myduration = std::chrono::duration<double, std::milli>;
myduration md = (finish - start);
// auto duration = std::chrono::duration_cast(finish - start);
std::cout << md.count() << "ms" << std::endl;
}
}
std::chrono::duration是一个模板类,关键代码摘录如下(格式有调整):
template
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::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 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)
}
std::ratio是一个模板类,关键代码摘录如下(格式有调整):
template
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 type;
};
第一个参数_Nx
代表了分子,第二个参数 _Dx
代表了分母。
num
是计算后的分子,den
是计算后的分母。在duration转换的时候会用到这两个值。
注:这里的计算是指约分,可以看到传入的分子和分母都除以了最大公约数。
num
是numerator
的缩写,表示分子。
den
是denominator
的缩写,表示分母。
函数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
.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
#include
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 type;
为了方便代码的书写,标准库提供了如下定义:
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
#include
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;
}
下图列出了duration可以进行的算术运算。例如:
运算所涉及的两个duration的单位类型可以不同:
std::chrono::seconds d1(42); //42秒
std::chrono::milliseconds d2(10); //10毫秒
d1 - d2; //返回41990个毫秒为单位的一个duration,42000-10=41990
d1 < d2; //返回false
std::chrono::duration<int, std::ratio<1, 3>> d3(1); //1/3秒
std::chrono::duration<int, std::ratio<1, 5>> d4(1); //1/5秒
d3 + d4; //返回8/15秒,1/3+1/5=8/15
d3 < d4; //返回false
template<typename V,typename R>
std::ostream& operator<<(std::ostream& s, const std::chrono::duration<V, R>& d)
{
s << "[" << d.count() << " of " << R::num << "/" << R::den << "]";
return s;
}
int main()
{
std::chrono::milliseconds d(42);
std::cout << d << std::endl;
std::chrono::seconds sec(55);
//错误的,默认不能将秒转换为分钟
std::chrono::minutes m1 = sec;
//正确的,可以使用duration_cast,将秒转换为分钟
std::chrono::minutes m2 = std::chrono::duration_cast<std::chrono::minutes>(sec);
std::chrono::duration<double, std::ratio<60>> halfMin(0.5);
//错误,halfMin的tick为double类型,s1的tick默认为int类型
std::chrono::seconds s1 = halfMin;
//正确,使用duration_cast强制转换
std::chrono::seconds s2 = std::chrono::duration_cast<std::chrono::seconds>(halfMin)
std::chrono::milliseconds ms(7255042);
std::chrono::hours hh = std::chrono::duration_cast<std::chrono::hours>(ms);
std::chrono::minutes mm = std::chrono::duration_cast<std::chrono::minutes>(ms%std::chrono::hours(1));
std::chrono::seconds ss = std::chrono::duration_cast<std::chrono::seconds>(ms%std::chrono::minutes(1));
std::chrono::milliseconds msec = std::chrono::duration_cast<std::chrono::milliseconds>(ms%std::chrono::seconds(1));
std::cout << "raw: " << hh << "::" << mm << "::" << ss << "::" << msec << std::endl;
std::cout << " " << setfill('0') << setw(2) << hh.count() << "::"
<< setw(2) << mm.count() << "::"
<< setw(2) << ss.count() << "::"
<< setw(3) << msec.count() << std::endl;
rs(1)轻松处理剩余的毫秒,那么毫秒又被转换为分钟
std::chrono::milliseconds ms(7255042);
std::chrono::hours hh = std::chrono::duration_cast<std::chrono::hours>(ms);
std::chrono::minutes mm = std::chrono::duration_cast<std::chrono::minutes>(ms%std::chrono::hours(1));
std::chrono::seconds ss = std::chrono::duration_cast<std::chrono::seconds>(ms%std::chrono::minutes(1));
std::chrono::milliseconds msec = std::chrono::duration_cast<std::chrono::milliseconds>(ms%std::chrono::seconds(1));
std::cout << "raw: " << hh << "::" << mm << "::" << ss << "::" << msec << std::endl;
std::cout << " " << setfill('0') << setw(2) << hh.count() << "::"
<< setw(2) << mm.count() << "::"
<< setw(2) << ss.count() << "::"
<< setw(3) << msec.count() << std::endl;
[外链图片转存中…(img-fnykkV5i-1673943125857)]