C++11
引入chrono类来表示时间中的一些概念,以及进行时间相关的计算,这是因为传统使用算术的方式来表示时间间隔和时间点本身这是很模凌两可的。通过使用chrono类库,使用不同的类型来表示不同的时间概念,这有助于通过编译器在编译时来帮助程序员发现一些逻辑错误,为此chrono抽象了如下几个概念:
duration
一个时间的duration表示的就是一个时间间隔,比如:3秒、3分钟、3小时等。在chrono类库中就分别有对应的时间单位来表示duration。std::chrono::seconds表示的是秒,是时间的间隔单位,其实现如下:
class seconds {
int64_t sec_;
public:
seconds() = default;
//....
};
就是一个类中包含了一个int64_t的成员来保存秒数,很简单,其次为了区分秒数和普通的数值之间的差异,seconds类在设计的时候就禁止数值隐式转换为seconds类,所以下面这样的操作编译不会通过:
seconds s = 3; // 编译出错
seconds s1{3}; // 这样是可以的
s1 = 4; // 编译出错
std::cout << s << "\n"; // 很不幸目前还不可以
std::cout << s.count() << "s\n"; // 这样是可以的
seconds s2 = 3s; // C++14支持
seconds s3 = s2 + s1; // 支持基本的算术操作,但是不支持seconds和一个普通数值进行算术操作
seconds::min(); // 获取秒类型可以表示的范围,
seconds::max();
除了seconds外,chrono还提供了nanoseconds、microseconds 、milliseconds、minutes、hours。用法基本上seconds一致。这些类型之间的转化规则chrono都已经帮我们做好了,例如: 1seconds == 1000milliseconds,而且这种转换是隐式的。例如下面这段代码:
minutes s1{2};
seconds s2 = s1; // s1隐式转换为seconds
seconds s3 = s2 + s1; // s1隐式转换为seconds,然后运算
但是这种转换有一个前提就是,转换必须时向下转换,比如hours转换minutes,minutes转seconds等,如果想向上转换就需要使用duration_cast显式的进行转换,例如下面这段代码:
seconds s2{1000};
minutes s3 = duration_cast<minutes>(s2);
chrono虽然已经内置了大多数的时间单位,但是它也提供了自定义类型的方法,你可以通过这个方法自定义出另外一种新的时间单位,你也可以定制你的时间单位的内部存储,默认的chrono提供的时间单位都是使用64位大小的存储,如果你想使用32位的存储你也可以进行定制,定制的方法如下:
using seconds32 = std::chrono::duration<int32_t>; // 使用32位来表示seconds
using new_type = std::chrono::duration<int64_t>; // 自定义新的new_type单位
using float_type = std::chrono::duration<float>; // 支持浮点数的新单位
默认提供的时间单位都是整型的,通过上面的办法你甚至可以自定义出支持浮点数的时间单位,只需要设置其类型为double或者float即可。虽然通过上面的方法可以对时间单位进行定制了,但是如何自定义该单位和其他时间单位的转换规则呢?默认的时间单位都有默认的转换规则。这个时候chrono提供了另外一个新的概念ratio,通过这个概念来决定转换的规则。
ratio
ration对应到时间单位的转换比例,是一个模版,提供了两个模版参数,默认提供好一系列的模版实例:
typedef ratio<1, 1000000000000000000> atto;
typedef ratio<1, 1000000000000000> femto;
typedef ratio<1, 1000000000000> pico;
typedef ratio<1, 1000000000> nano;
typedef ratio<1, 1000000> micro;
typedef ratio<1, 1000> milli;
typedef ratio<1, 100> centi;
typedef ratio<1, 10> deci;
typedef ratio< 10, 1> deca;
typedef ratio< 100, 1> hecto;
typedef ratio< 1000, 1> kilo;
typedef ratio< 1000000, 1> mega;
typedef ratio< 1000000000, 1> giga;
typedef ratio< 1000000000000, 1> tera;
typedef ratio< 1000000000000000, 1> peta;
typedef ratio< 1000000000000000000, 1> exa;
至于为什么seconds和milliseconds为什么可以相互转换,这是因为它们在创建的时候就已经设置好了默认的转换规则,还记得上文中的duration吗?可以用来自定义时间间隔单位,默认的seconds、milliseconds等就是通过std::chrono::duration创建的,代码如下:
using nanoseconds = duration<int_least64_t, nano>
using microseconds = duration<int_least55_t, micro>
using milliseconds = duration<int_least45_t, milli>
using seconds = duration<int_least35_t, ration<1>>
using minutes = duration<int_least29_t, ration<60>>
using hours = duration<int_least23_t, ration<3600>>
template <class Rep, class Period = ratio<1>>
class duration {
public:
using rep = Rep;
using period = Period;
// ... };
通过上面的代码可以看出,默认的时间间隔单位都带有一个时间转换比例,是通过duration模版类的第二个参数设置的,因此如果想给自定义的时间间隔单位设置转换比例,就可以具现化一个ratio类,然后传给duration即可。
time point
无论是生活中,还是在程序编码的时候,光一个时间间隔是不够的,我们常常需要具体的时间点,比如:2017年12月21日10时50封23秒,对于这样的概念,chrono库使用time_point来表示,这是一个模版类,其实现如下:
template <class Clock,
class Duration = typename Clock::duration>
class time_point {
Duration d_;
public:
using clock = Clock;
using duration = Duration;
// ... };
有两个模版参数,第一个是使用何种时钟,第二个是时间间隔,可见时间点的底层是用的duration来存储的,那么很自然两者也是可以相互进行转换的,通过time_point的time_since_epoch方法就可以将一个时间点,转换为时间间隔。和时间间隔一样,不同类型的时间点之间也可以相互转换,默认是隐式的向下转换,如果想向上转换就必须显示的使用time_point_cast来进行转换,代码如下:
using namespace std::chrono;
template <class D>
using sys_time = time_point<system_clock, D>;
sys_time<seconds> tp{5s}; // 5s
sys_time<milliseconds> tp2 = tp; // 5000ms 隐式的向下转换
tp = time_point_cast<seconds>(tp2); // 5s 向下转换,需要显示的使用time_point_cast
clocks
最后讨论下时钟的概念,在讨论time point的时候就谈到过时钟,说简单点时钟就是时间点,时间间隔以及一顿静态方法的集合。它的实现可以简单的表示为下面这种形式:
struct some_clock
{
using duration = chrono::duration<int64_t, microseconds>;
using rep = duration::rep;
using period = duration::period;
using time_point = chrono::time_point<some_clock>;
static constexpr bool is_steady = false;
static time_point now() noexcept;
};
每一个时间点都关联了一个时钟,不同时钟的时间点事没有办法相互转换的,代码如下:
system_clock::time_point tp = system_clock::now();
steady_clock::time_point tp2 = tp; // no viable conversion
默认系统提供了两张时钟,一个就是system_clock,另外一个则是steady_clock ,前者表示的就是墙上时钟,和挂在墙上的时钟保持一样,但是这个值并不一定是单调的,因为操作系统可能会修改时间,导致system_clock返回的时间不单调。而steady_clock表示的是单调时钟,随着物理时间向前,这个时钟的时间点不会减少,因为该时钟与墙上的挂钟时间无关,最适合进行间隔的测量。