CPPCON2016 - A <chrono> Tutorial

CPPCON2016: A <chrono> Tutorial

此演讲对<chrono>头文件有很好的解释,而且入门很轻松。

chrono是c++关于时间的头文件,是C++11引入的。之所以引入这样一个头文件有诸多原因,一是C++尚缺少对时间类型的诠释,使用API时可读性很低。比如

sleep(10);

我们很难说这个10的单位究竟是什么,也很难在不同的时间单位之间去手动转换。chrono基本解决了可读性的问题。

因为C++有自定义的后缀,叫什么我忘了,你可以这样使用一个chrono类。

sleep(10ms);

chrono还能帮助编译器在编译器找到一些逻辑错误,这是通过区别类型和概念实现的。

演讲主题主要是

  • time duration
  • time points
  • clock

Time Duration

duration类表示一个时间段,同一采用duration类实现,他是一个模板类,通过别名实现良好的可读性,比如

不过演讲是从通俗易懂的概念讲起的。

让我们从seconds开始。

首先有几个要求:

  • seconds是一个算术类型
  • sizeof(seconds) == 8
  • 平凡类:平凡复制,构造,析构,移动等。

它的定义很像一个类定义

class seconds{
	int64_t sec_;
public:
	seconds() = default;
	//etc
};

在构造的时候,seconds不允许int类型转换,即你不能这样使用

seconds s = 12;
//or
void f(seconds s){ cout<<s.count()<<endl;}
f(12); //error
//这是就是chrono的好处之一,因为在编译的时候这里告诉你无法从int类型转换到seconds类型,这表示你必须指定12的单位,比如f(12s)(in c++14)。这大大提高了代码的可读性,在外部查看时我们能知道函数中使用12s这一时间而不是看着12发愣,思考内部究竟是使用多少“12”

但是这样是OK的。

seconds s{12};

目前你可以这样输出seconds对象,而不是像演讲那样提到的

cout<< s <<endl;

除此之外,你可使用s.count()来获取内部的数值进行输出。

前面提到了seconds很像算术类型,即你可以对两个seconds进行加减,但是同样注意int类型不可参与运算。同时,你还可以使用seconds进行基础的六种比较操作(==,!=,>,>=,<,<=)。

演讲中演示了,实际上seconds的算术操作和int类型操作所花费的时间是一致的(他们的汇编代码完全相同),所以duration对象的算术操作完全是zero abstraction的。

seconds对象有范围的概念,因为他们的duration对象,所以时间长度取决于内部数据能表示的最大长度以及时间比例(ratio),比如

seconds m = seconds::min();
seconds M = seconds::max();
// +/- 292billion years

虽然此范围极大,但是还是尽量避免溢出吧,尤其是对于时间单位比较小的类来说。

接下来的一个问题就是代码中时间单位的转换,比如我们原先代码都是基与seconds编写,都是以秒为单位,现在我们像重构,想让这些代码以毫秒为单位,该如何操作?

chrono内部实际上都帮你处理好了,你只需要改变内部的类型即可。

比如

void f(seconds d){
	cout<<d.count()<<"s"<<endl;
}
//then……
void f(milliseconds d){
    cout<<d.count()<<"ms"<<endl;
}

你只需要像这样在需要的地方直接改变类,即便外部传入参数的是12s,在转换毫秒的时候它会自动转成12000ms

而且这种自动行为同样发生在算数和比较操作上,你完全可以将seconds和milliseconds相加或是比较,他们的行为完全符合你的直觉。

milliseconds lhs = 1200ms;
seconds rhs = 5s;
auto res = lhs + rhs;
//typeof(res) is milliseconds and the value of res is 6200ms

注意,目前都是将seconds转换到milliseconds,这种转换是自动的,因为任何整数秒都可以用整数的毫秒表达, 但是反过来,整数的毫秒无法用整数的秒表示,这会损失信息。

这总转换不是自动的,你可以使用duration_cast来强制转换,它会损失信息并转换到你想要的时间单位上。

在c++17你可以将转换往不同的方向进行,比如floor<duration>总是将转换操作往负无穷的方向进行,round<duration>是往最近的数值或是偶数进行靠拢,ceil<duration>往正无穷的方向进行。

chrono头文件有很多单位你可以使用比如hours,years,microseconds……等等,他们都是duration类的别名,不同的是他们的ratio(时间比例)不同。

比如hours

using hours = duration<int,ratio<3600>>;

因为1小时有3600秒,所以ratio<3600>,前面的是你用什么类型来存hours的数值。比如seconds使用就是long long类型。

另外,如果你用浮点类型来表示数值的化,你就能不适用duration_cast且去转换了。

using fseconds = chrono::duration<float>;
void f(fseconds sec){//do something}

f(21321ms); //OK

根据ratio和duration,你甚至可以创建自己的时间单位。比如你的世界是以 帧来运行,而一秒钟固定24帧,那么……

using frames = chrono::duration<long long,ratio<1,24>>;

ratio模板有两个long long类型的非类型模板参数,前面是分子,后面是分母。根据ratio,chrono能根据各自的比例来进行转换和计算

Time Points

time points顾名思义,它代表着是一个时间点。

根据规定,初始时间点应该是从1970-1-01-0:00(epoch)开始计算,比如我初始化一个10000s的time point。

time_point<system_clock,seconds> tp{10000s};

那么它代表的时间就是1970-1-01-2:46:40

timepoints内部并不存储clock类型,内部只存一个duration类型的值,大概长成这个样子:

template<typename Clock,typename Duration = typename Clock::duration>
class time_point{
	Dutation d;
public:
	using clock = Clock;
    using duration = Duration;
    //...
};

time point可计算,一般会用到两种:

  • time point - time point = duration
  • time point +/- duration = time point
  • time point++ / ++time point

time point不可相加,相乘,这些都会在编译期提示你错误信息。

time points之间也可以根据duration不同的ratio进行相互转换,比如:

time_point<system_clock,hours> tph{20h};
time_point<system_clock,seconds> tps{tps};
auto duration = tps - tph;// seconds

对应的,time point也有一个time_point_cast来允许你强制转换:

time_point<system_clock,minutes> tpm=time_point_cast<minutes>(time_point<system_clock,seconds>(21321s));

以及c++17也包括对time point进行rounding运算,比如floor ceil等。

clocks

clock是将一个duration,一个time point和一个能获得当前时间点的静态函数捆绑在一起的一个结构体。

它大概是这个样子:

struct some_clock{
	using duration = chrono::duration<int64_t,microseconds>;
	using rep = duration::rep;
	using period = duration::period;
	using time_point = chrocho::time_point<some_clock>;
	static constexpr bool is_steady = false;
	
	static time_point now() noexcpet;
};

如果is_steady为true,那么该clock不能被调整,它会一直转下去,如果为false,那么clock就可以被调整,比如说时间服务器调整闰秒。

每个time point对象都必须与一个clock关联,关联到不同clock的time point之间无法转换。

目前c++11有三种clock(c++20新增6种)

  • system_clock
  • steady_clock
  • high_resolution_clock (ignore this)

如果你一个与日期相关的time point,使用system_clock

如果你想计时,类似秒表的功能,使用steady_clock

posted @ 2022-07-23 13:27  ᴮᴱˢᵀ  阅读(56)  评论(0)    收藏  举报