C++11中的时间库std::chrono


前言

时间是宝贵的,我们无时无刻不在和时间打交道,这个任务明天下班前截止,你点的外卖还有5分钟才能送到,那个程序已经运行了整整48个小时,既然时间和我们联系这么紧密,我们总要定义一些术语来描述它,像前面说到的明天下班前、5分钟、48个小时都是对时间的描述,程序代码构建的程序世界也需要定义一些术语来描述时间。

今天要总结学习的是 std::chrono 库,它是 C++11 标准时从 boost 库中引入的,其实在 C++ 中还有一种 C 语言风格的时间管理体系,像我们常见的函数 time()clock()localtime()mktime() 和常见的类型 tmtime_tclock_t 都是 C 语言风格的时间管理体系。

std::chrono 这个库之前接触的不多,C++20 标准都出了,C++11 引入的这个库还没怎么用过,整天和 time()localtime()tm 打交道,最近工作中换了项目,代码中出现了 std::chrono 的使用,是时候好好学习总结一下了。

chrono 的概况

  • 头文件 #include <chrono>
  • 命名空间 std::chrono

这个库从 C++11 引入标准之后,每个版本都有所修改,不过核心内容变化不是太大,他定义了三种主要类型,分别是 durationsclockstime points,以及围绕这些类型的一些工具函数和衍生的定义。

chrono 的核心内容

duration

这个模板类用来表示时间间隔,我们知道时间的基本单位是秒,这个类的对象所表示的时间间隔也是以秒为单位的,它的定义如下:

template<class Rep, class Period = std::ratio<1>>
class duration;

Rep 表示一种数值类型,用来描述周期 Period 的数值类型,比如可以是 intfloat 等,而 Period 的类型是 std::ratio,同样是一个模板类,实际表示的是一个有理数,像100、0、1/1000(千分之一)等等。

std 这个命名空间下有很多已经定义好的有理数,可以举几个常见的头文件 <ratio> 中的例子:

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>         // 千

比如我们想定义一个整数类型的100秒的时间间隔类型可以使用:

typedef std::chrono::duration<int, std::ratio<100,1>> my_duration_type;

当然也可以简写成:

typedef std::chrono::duration<int, std::hecto> my_duration_type;

如果我们想定义一个整数类型1分钟的时间间隔类型可以写成:

typedef std::chrono::duration<int, std::ratio<60,1>> my_minute_type;

因为这种时、分、秒的时间表示在代码逻辑中很常用,所有在 std::chrono 命名空间下已经定义好了一些时间间隔类型:

std::chrono::nanoseconds    duration</*signed integer type of at least 64 bits*/, std::nano>
std::chrono::microseconds   duration</*signed integer type of at least 55 bits*/, std::micro>
std::chrono::milliseconds   duration</*signed integer type of at least 45 bits*/, std::milli>
std::chrono::seconds        duration</*signed integer type of at least 35 bits*/>
std::chrono::minutes        duration</*signed integer type of at least 29 bits*/, std::ratio<60>>
std::chrono::hours          duration</*signed integer type of at least 23 bits*/, std::ratio<3600>>

另外还有一个很重要的成员函数 count(),用来获得指定的时间间隔对象中包含多少个时间周期,接下来可以写个例子理解一下,我们用 duration 这个模板类来表示一下5分钟和12小时,看看他应该怎么使用,对于5分钟你可以看成是 5 个 1 分钟或者 1 个 5 分钟,或者更变态你可以看成 2.5 个 2 分钟,而 12 小时一般会看成是 12个 1 小时,你当成 0.5 个 1 天也是可以的:

#include <chrono>
#include <iostream>
int main()
{
    // 以下为5分钟表达
    std::chrono::minutes minute1{5}; // 5个1分钟
    std::chrono::duration<int, std::ratio<5*60, 1>> minute2{1}; // 1个5分钟
    std::chrono::duration<double, std::ratio<2*60, 1>> minute3{2.5}; // 2.5个2分钟

    std::cout <<  "minutes1 duration has " << minute1.count() << " ticks\n"
              <<  "minutes2 duration has " << minute2.count() << " ticks\n"
              <<  "minutes3 duration has " << minute3.count() << " ticks\n";

    // 一下为12小时表达
    std::chrono::hours hours1{12}; // 12个1小时
    std::chrono::duration<double, std::ratio<60*60*24, 1>> hours2{0.5}; // 0.5个1天

    std::cout <<  "hours1 duration has " << hours1.count() << " ticks\n"
              <<  "hours2 duration has " << hours2.count() << " ticks\n";

    // 使用 std::chrono::duration_cast<T> 将分钟间隔转化成标准秒间隔
    std::cout <<  "minutes1 duration has " <<
        std::chrono::duration_cast<std::chrono::seconds>(minute1).count() << " seconds\n";
}

上述代码中还使用了 std::chrono::duration_cast<T>() 函数,用于各种时间间隔的换算,运行结果如下:

minutes1 duration has 5 ticks
minutes2 duration has 1 ticks
minutes3 duration has 2.5 ticks
hours1 duration has 12 ticks
hours2 duration has 0.5 ticks
minutes1 duration has 300 seconds

clock

从名字可以看出这个类叫做时钟,时钟是用来看时间和计时的,常用的两个类是 system_clocksteady_clock,在 C++20 标准中又加入了多种内容,现在我们先来看看这两个常用类。

从这一部分开始类的定义让人有些迷糊,其实 clock 引用了 std::chrono::duration 和后面要说的 std::chrono::time_point, 而 std::chrono::time_point 又引用了 std::chrono::duration 和现在要讲的 std::chrono::system_clockstd::chrono::steady_clock,如果只看定义很容易被绕晕,所以还是先做个练习实验一下。

system_clock

这个类被称为系统内时钟,当修改系统时钟时可能会改变其单调递增的性质,静态成员函数有 now()to_time_t()from_time_t() 三个,关于它的单调性被修改举个例子,一般认为时间一直是递增的,但是当你现在调用一次函数 now(),然后把时间往过去调1天,然后再调用 now() 函数,就会发现新得到的时间“变小”了。

也因为这样它会受到 NTP(Network Time Protocol,网络时间协议)的影响,但是不会受时区和夏令时的影响(其实很多国家早就废除夏令时了)。

下面写个例子练习一下,例子中使用了 now()to_time_t()from_time_t() 三个函数,不清楚的时候可以对照一下:

#include <chrono>
#include <iostream>
int main()
{
    std::chrono::duration<int, std::ratio<60*60*24> > one_day(1);

    // 根据时钟得到现在时间
    std::chrono::system_clock::time_point today = std::chrono::system_clock::now();
    std::time_t time_t_today = std::chrono::system_clock::to_time_t(today);
    std::cout <<  "now time stamp is " << time_t_today << std::endl;
    std::cout <<  "now time is " << ctime(&time_t_today) << std::endl;


    // 看看明天的时间
    std::chrono::system_clock::time_point tomorrow = today + one_day;
    std::time_t time_t_tomorrow = std::chrono::system_clock::to_time_t(tomorrow);
    std::cout <<  "tomorrow time stamp is " << time_t_tomorrow << std::endl;
    std::cout <<  "tomorrow time is " << ctime(&time_t_tomorrow) << std::endl;


    // 计算下个小时时间
    std::chrono::system_clock::time_point next_hour = today + std::chrono::hours(1);
    std::time_t time_t_next_hour = std::chrono::system_clock::to_time_t(next_hour);
    std::chrono::system_clock::time_point next_hour2 = std::chrono::system_clock::from_time_t(time_t_next_hour);

    std::time_t time_t_next_hour2 = std::chrono::system_clock::to_time_t(next_hour2);
    std::cout <<  "tomorrow time stamp is " << time_t_next_hour2 << std::endl;
    std::cout <<  "tomorrow time is " << ctime(&time_t_next_hour2) << std::endl;

    return 0;
}

运行结果如下:

now time stamp is 1586662332
now time is Sun Apr 12 11:32:12 2020

tomorrow time stamp is 1586748732
tomorrow time is Mon Apr 13 11:32:12 2020

tomorrow time stamp is 1586665932
tomorrow time is Sun Apr 12 12:32:12 2020

steady_clock

这是一个单调时钟,一旦启动之后就与系统时间没有关系了,完全根据物理是时间向前移动,成员函数只有一个 now(),通常可以用来计时,使用方法与 system_clock 相比简单许多,下面写个小例子。

#include <chrono>
#include <iostream>
int main()
{
    // 先记录程序运行时间
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();

    volatile int nDstVal, nSrcVal;
    for (int i = 0; i < 1000000000; ++i)
        nDstVal = nSrcVal;

    // 做差值计算耗时
    std::chrono::duration<double> duration_cost = std::chrono::duration_cast<
        std::chrono::duration<double> >(std::chrono::steady_clock::now() - start);
    std::cout <<  "total cost " << duration_cost.count() << " seconds." << std::endl;

    return 0;
}

运行结果如下:

total cost 1.9424 seconds.

 

time point

这个类与 duration 类似,同样是模板类,表示具体的时间点,比如今天 18:00 开饭,明天上午 10:00 发版本,今年 5 月 1 日可能因为疫情不让出去玩了,像这些具体的时间点可以使用 std::chrono::time_point 来表达,它的定义如下:

template<class Clock, class Duration = typename Clock::duration>
class time_point;

首先这个类是在 std::chrono 这个命名空间下,但是你会经常看到以下这种写法:

std::chrono::system_clock::time_point today = std::chrono::system_clock::now();
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();

好像 time_point 又在 std::chrono::system_clockstd::chrono::steady_clock 范围内,实际上这两个范围内的 time_point 引用的是 std::chrono::time point,看看 std::chrono::system_clock 的定义能明白一些。

class system_clock {
public:
  using rep = /*see description*/ ;
  using period = ratio</*unspecified*/, /*unspecified*/ >;
  using duration = chrono::duration<rep, period>;
  using time_point = chrono::time_point<system_clock>;
  static constexpr bool is_steady = /*unspecified*/ ;
  static time_point now() noexcept;
  // Map to C API
  static time_t to_time_t (const time_point& t) noexcept;
  static time_point from_time_t(time_t t) noexcept;
};

对照上面的定义可以知道,std::chrono::system_clock::time_point 实际上 std::chrono::time_point<system_clock>,这几个时间类的定义相互引用,看到这一部分的时候一定不要烦躁,一步步推导分析其中的关系。

time_point 这个类有一个成员函数 time_since_epoch() 用来获得 1970-01-01 00:00:00time_point 时间经过的 duration, 返回的 duration 的单位取决于 timepoint 定义时的 duraion 的单位,不过你也可以得到 duration 之后使用 std::chrono::duration_cast<T>() 函数来转化。

#include <chrono>
#include <iostream>
int main()
{
    // 获得epoch 和 now 的时间点
    std::chrono::time_point<std::chrono::system_clock> epoch =
        std::chrono::time_point<std::chrono::system_clock>{};
    std::chrono::time_point<std::chrono::system_clock> now =
        std::chrono::system_clock::now();

    // 显示时间点对应的日期和时间
    time_t epoch_time = std::chrono::system_clock::to_time_t(epoch);
    std::cout << "epoch: " << std::ctime(&epoch_time);
    time_t today_time = std::chrono::system_clock::to_time_t(now);
    std::cout << "today: " << std::ctime(&today_time);

    // 显示duration的值
    std::cout << "seconds since epoch: "
        << std::chrono::duration_cast<std::chrono::seconds>(epoch.time_since_epoch()).count()
        << std::endl;

    std::cout << "today, ticks since epoch: "
        << now.time_since_epoch().count()
        << std::endl;

    std::cout << "today, hours since epoch: "
        << std::chrono::duration_cast<std::chrono::hours>(now.time_since_epoch()).count()
        << std::endl;

    return 0;
}

运行结果如下:

epoch: Thu Jan  1 08:00:00 1970
today: Sun Apr 12 12:30:04 2020
seconds since epoch: 0
today, ticks since epoch: 1586665804624992500
today, hours since epoch: 440740

从运行结果来看,epoch 的时间点是 Thu Jan 1 08:00:00 1970,为什么不是 1970-01-01 00:00:00 呢?那是因为我们在东8区,格林威治时间为
1970-01-01 00:00:00 的时候,我们的时间就是 Thu Jan 1 08:00:00 1970,这样看来 std::ctime() 这个函数考虑了时区的影响,相同的代码如果在韩国同时运行得到的可能就是 epoch: Thu Jan 1 09:00:00 1970

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值