距离上次更新本文已经过去了 208 天,文章部分内容可能已经过时,请注意甄别

1. 说明

std::choro::duration 是 C++11 引入的一个用于计算时间滴答周期的类,与之配合使用的是 std::ratio 类,该类是一个分数类,为精确表示分数提供了一个方式。

2.std::ratio

我们知道,对于计算机来说,使用浮点类型是会有精度缺失的。所以 std::ratio 类便提供了分子和分母,

cpp
1
2
3
4
template<
std::intmax_t Num,
std::intmax_t Denom = 1
> class ratio;

该类的模板定义如下,Num 是分子,Denom 是分母。不传入分母的时候,分母默认为 1。

假设我们需要表示 1 秒的千分之一(即一毫秒)就可以用如下方式来定义一个 ratio

cpp
1
typedef ratio<1,1000> milli;

代表 1/1000 这个分数;

cpp 中自带的几个 ratio

为了方便标识单位之间的差距(单位换算),cpp 中预定义了一些常用的 ratio

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;

我们可以用类作用域符号:: 来访问到这些比例的分子和分母。

cpp
1
2
std::cout << std::centi::num << "/" << std::centi::den << std::endl;
// 运行结果为 1/100

3.std::chrono::duration

这个类是用来表示一个时间的周期 / 长度的,其基于 1s 为基本单位,用 ratio 来表示和 1s 相比的偏移量。

在 cppreference 上的介绍如下

Class template std::chrono::duration represents a time interval.
It consists of a count of ticks of type Rep and a tick period, where the tick period is a compile-time rational constant representing the number of seconds from one tick to the next.
The only data stored in a duration is a tick count of type Rep. If Rep is floating point, then the duration can represent fractions of ticks. Period is included as part of the duration’s type, and is only used when converting between different durations.

该类的定义如下

cpp
1
2
3
4
template<
class Rep,
class Period = std::ratio<1>
> class duration;

其中 Rep 是一个变量类型,可以传入 int、float、double 等类型;

Period 是一个 std::ratio,可以传入基于一秒的比例,这个比例就代表了时间的单位;

cpp 中自带的几个 chrono

为了方便我们使用,cpp 中自带了几个用于表示时间的 chrono 的定义

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    /// nanoseconds
using nanoseconds = duration<_GLIBCXX_CHRONO_INT64_T, nano>;

/// microseconds
using microseconds = duration<_GLIBCXX_CHRONO_INT64_T, micro>;

/// milliseconds
using milliseconds = duration<_GLIBCXX_CHRONO_INT64_T, milli>;

/// seconds
using seconds = duration<_GLIBCXX_CHRONO_INT64_T>;

/// minutes
using minutes = duration<_GLIBCXX_CHRONO_INT64_T, ratio< 60>>;

/// hours
using hours = duration<_GLIBCXX_CHRONO_INT64_T, ratio<3600>>;

#if __cplusplus > 201703L
/// days
using days = duration<_GLIBCXX_CHRONO_INT64_T, ratio<86400>>;

/// weeks
using weeks = duration<_GLIBCXX_CHRONO_INT64_T, ratio<604800>>;

/// years
using years = duration<_GLIBCXX_CHRONO_INT64_T, ratio<31556952>>;

/// months
using months = duration<_GLIBCXX_CHRONO_INT64_T, ratio<2629746>>;
#endif // C++20

代码里面出现的宏定义其实就是 int64_t 的别名。

cpp
1
## define _GLIBCXX_CHRONO_INT64_T int64_t

这些变量之前在学习 C++ 线程休眠的时候就已经遇到过了,从中也能看到 std::ratio 在 chrono 中代表的比例关系,比如一小时是 3600 秒,那么就需要传入比例 std::ratio<3600,1>,上面的代码里面省略了 1 是因为 ratio 的分母缺省值就是 1。

代码示例 1

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <chrono>

int main()
{
typedef std::chrono::duration<int, std::ratio<1, 100000000>> shakes;
typedef std::chrono::duration<int, std::centi> jiffies;
typedef std::chrono::duration<float, std::ratio<12096,10000>> microfortnights;
typedef std::chrono::duration<float, std::ratio<3155,1000>> nanocenturies;

std::chrono::seconds sec(1);

std::cout << "1 second is:\n";

std::cout << std::chrono::duration_cast<shakes>(sec).count()
<< " shakes\n";
std::cout << std::chrono::duration_cast<jiffies>(sec).count()
<< " jiffies\n";
std::cout << std::chrono::duration_cast<microfortnights>(sec).count()
<< " microfortnights\n";
std::cout << std::chrono::duration_cast<nanocenturies>(sec).count()
<< " nanocenturies\n";
return 0;
}

上面的代码示例运行结果如下

plaintext
1
2
3
4
5
6
> g++ test.cpp -o test -std=c++17 && ./test
1 second is:
100000000 shakes
100 jiffies
0.82672 microfortnights
0.316957 nanocenturies

我们能看到,第一个里面传入的比例是 1/100000000,这就代表 1 秒包含了 100000000 个 shakes,假设我们需要一个休眠 1/100000000 秒的操作,就可以通过 std::this_thread::sleep_for 和这个 shakes 变量来进行精确的定时休眠。

代码示例 2

让当前线程休眠 100 毫秒,有几种不同的方式,都是等价的。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <thread> 
#include <chrono>
#include <iostream>

int main()
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// or
std::this_thread::sleep_for(std::chrono::duration<long long, std::milli>(100));
// or
// typedef ratio<1, 1000> milli;
std::this_thread::sleep_for(std::chrono::duration<long long, std::ratio<1, 1000> >(100));
}

4.std::chrono::duration_cast

为了方便在不同的时间单位之间进行转换,CPP 提供了一个特殊的 cast 来进行处理

cpp
1
std::chrono::duration_cast<目标类型>(源类型对象);

比如可以用下面的代码将 3 秒转换为毫秒,注意圆括号里面的传参是源类型的一个对象,不能只传入类型(那样就不是变量类型转换了)

cpp
1
2
3
4
5
6
7
8
9
#include <chrono>
#include <iostream>

int main()
{
std::chrono::milliseconds ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::minutes(3));
std::cout << "3 minutes equals to " << ms.count() << " milliseconds\n";

}

运行结果如下

plaintext
1
2
> g++ test.cpp -o test -std=c++17 && ./test
3 minutes equals to 180000 milliseconds

The end

基本要了解的内容就这些了。

参考文档