C++11中引入<chrono>
用于处理计时相关的操作。C++14和17中对其作了一些小的改进。在即将到来的C++20中chrono即将迎来一次大范围的扩充。导致原本就比较复杂的chrono变得愈加复杂。本文局限于C++11中的chrono,做简单介绍。本文的大部分内容参考自:https://en.cppreference.com/w/cpp/chrono。
C++中本没有处理计时的库,所以之前只能用C语言的ctime库。C++11中引入<chrono>
,以模板的方式定义了几个计时相关的类型:
- 表示时钟:chrono::system_clock、chrono::steady_clock、chrono::high_resolution_clock等
- 表示时刻:chrono::time_point,时刻必须是对于某个时钟的
- 表示时长:chrono::duration,时长可以选择
chrono::duration的模板声明如下:
template<
class Rep,
class Period = std::ratio<1>
> class duration;
其中Rep用来表示滴答的类型,用于决定其范围,Period用来表示滴答的精度(换算成秒)。由于Rep可以采用浮点类型,所以还有另外一个模板类chrono::duration_values用来返回Rep的零值,可表示的最小值/最大值等。duration的count()
返回时长的滴答数。
预定义的时长类型:
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>>
C++14还允许使用字面值来生成上面的时长:
using namespace std::literals::chrono_literals;
0.5h;
30min;
100s;
// ...
chrono::duration_cast可以在不同的时长类型之间进行转化,这其中避免不了精度的差异,需要做舍入或者进位操作。C++17中引入的floor、ceil、round等成员可以帮助设置取舍规则。
chrono::system_clock用来表示系统时钟,其成员now()
可以用来获取当前系统时钟,返回值的类型是和system_clock关联的一个时刻:chrono::time_point<std::chrono::system_clock>
。所谓时刻,就是某个时钟的起点到当前的时长。这里有个起点(epoch)的概念。因为计算机没办法表示无限大的数值,所以给时钟设置一个起点,才能计算它时刻。chrono::time_point的成员time_since_epoch()
返回该时刻距离起点的时长。同一时钟上的时刻可以用chrono::time_point_cast来转化其时长单位。
下面是一段示例代码:
#include <ctime>
#include <chrono>
#include <iostream>
using namespace std;
int main()
{
const auto epoch = chrono::time_point<chrono::system_clock>{};
auto epoch_time = chrono::system_clock::to_time_t(epoch);
cout << "epoch(ctime): " << ctime(&epoch_time);
cout << "epoch(gmtime): " << asctime(gmtime(&epoch_time));
cout << "duration since epoch: " << epoch.time_since_epoch().count() << endl;
const auto today = chrono::system_clock::now();
auto today_time = chrono::system_clock::to_time_t(today);
cout << "today(ctime): " << ctime(&today_time);
auto duration = today.time_since_epoch();
cout << "today: duration since epoch: " << duration.count() << endl;
cout << "today: duration (hours) since epoch: " << chrono::duration_cast<chrono::hours>(duration).count() << endl;
cout << "today: duration (minutes) since epoch: " << chrono::duration_cast<chrono::minutes>(duration).count() << endl;
return 0;
}
将上面的代码保存为showtime.cpp,然后可以用Visual Studio 2019的命令行:cl /EHsc /std:c++17 showtime.cpp
编译,运行结果为:
epoch(ctime): Thu Jan 1 08:00:00 1970
epoch(gmtime): Thu Jan 1 00:00:00 1970
duration since epoch: 0
today(ctime): Wed Sep 18 23:06:16 2019
today: duration since epoch: 15688191769794584
today: duration (hours) since epoch: 435783
today: duration (minutes) since epoch: 26146986
对上面例子的一些讲解:
- C++20之前,system_clock的epoch是可以由实现自行定义的,一般都是UTC时间1970年1月1日零点。C++20则对其进行强制规定,必须是1970年1月1日零点。从system_clock构造一个不带参数的time_point,其值就是epoch所在。
- C++20之前,time_pont要转化为
<ctime>
中的time_t才能通过ctime()、gmtime()、localtime()等函数将其转化为字符串。由于ctime()转化的时候会考虑时区,所以UTC时间1970年1月1日零点对应的中国时间要加上8小时。
system_clock
system_clock的滴答的精度和范围其实是由实现自定义的,下面的例子打印出当前系统的system_clock的精度和范围:
#include <chrono>
#include <iostream>
#include <locale>
#include <typeinfo>
#include <cxxabi.h>
using namespace std;
class MyNumPunct : public std::numpunct<char>
{
protected:
virtual char do_thousands_sep() const { return ','; }
virtual std::string do_grouping() const { return "\03"; }
};
int main()
{
using ScRep = chrono::system_clock::rep;
using ScPeriod = chrono::system_clock::period;
std::cout.imbue(locale(locale::classic(), new MyNumPunct));
cout << "period: " << ScPeriod::num << " / " << ScPeriod::den << endl;
auto nameOfScRep = typeid(ScRep).name();
cout << "sep: " << nameOfScRep << ", sizeof: " << sizeof(ScRep) << endl;
int status;
char *demangled_name = abi::__cxa_demangle(nameOfScRep, NULL, NULL, &status);
if(status == 0) {
cout << "sep (demangled): " << demangled_name << endl;
free(demangled_name);
}
return 0;
}
上面例子中的代码参考了:
- Print integer with thousands and millions separator
- Is it possible to print a variable’s type in standard C++?
- C++ Get name of type in template
其中使用到了GCC的abi,所以需要使用GCC来编译。
在Windows 10下输出:
period: 1 / 1,000,000,000
sep: x, sizeof: 8
sep (demangled): long long
duration_cast
不同的duration,由于其嘀嗒时长以及范围不同,转化的时候会有精度损失的情况存在,这种情况下需要显示调用duration_cast来转化。
这里有几个规则:
- 由低精度往高精度转的时候(比如从小时转为分钟),因为不会出现精度损失,所以不需要duration_cast
- 反而言之,由高精度往低精度转的时候(比如从分钟到小时),因为会出现精度损失,需要duration_cast
- 如果duration的范围是用整形表示的,那么转化为浮点型的时候不需要duration_cast,会有相应的构造函数进行处理,这里隐含的意义是,浮点数的表示范围一定比整型值大。可以用
chrono::treat_as_floating_point
来检查duration的范围是否是浮点树类型。 - 从浮点型的往整型值转的时候,不仅有可能出现精度损失,甚至可能出现未定义行为,因为浮点数的值有可能是NaN。
另外,C++17中还引入了floor, ceil, round, abs等还处理转化过程中的精度问题。
(完)