为什么 std::chrono::time_point 不够大,无法存储 struct timespec?

Why std::chrono::time_point is not large enough to store struct timespec?

我正在尝试最近的 std::chrono api,我发现在 64 位 Linux 架构和 gcc 编译器上 time_pointduration 类 无法以最高分辨率(纳秒)处理操作系统的最大时间范围。事实上,与 timespectimeval 相比,这些 类 的存储似乎是一种 64 位整数类型,它们在内部使用两个 64 位整数,一个用于秒,一个用于纳秒:

#include <iostream>
#include <chrono>
#include <typeinfo>
#include <time.h>

using namespace std;
using namespace std::chrono;

int main()
{
    cout << sizeof(time_point<nanoseconds>) << endl;                       // 8
    cout << sizeof(time_point<nanoseconds>::duration) << endl;             // 8
    cout << sizeof(time_point<nanoseconds>::duration::rep) << endl;        // 8
    cout << typeid(time_point<nanoseconds>::duration::rep).name() << endl; // l
    cout << sizeof(struct timespec) << endl;                               // 16
    cout << sizeof(struct timeval) << endl;                                // 16
    return 0;
}

64位Windows(MSVC2017)情况很相似:存储类型也是64位整数。在处理稳定(又名单调)时钟时这不是问题,但存储限制使得不同的 API 实现不适合存储更大的日期和更宽的时间跨度,从而为类似 Y2K 的错误创造了基础。问题是否得到承认?是否有更好的实施或 API 改进的计划?

这样做是为了让您获得最大的灵活性和紧凑的尺寸。如果您需要超精细的精度,通常不需要非常大的范围。而如果你需要非常大的范围,你通常不需要非常高的精度。

例如,如果您以纳秒为单位进行交易,您是否经常需要考虑超过 +/- 292 年?如果你需要考虑一个更大的范围,那么微秒给你 +/- 292 年。

macOS system_clock 实际上 returns 微秒,而不是纳秒。所以那个时钟可以运行从1970年开始29.2万年直到它溢出。

Windows system_clock 的精度为 100-ns 单位,因此范围为 +/- 29200 年。

几十万年还不够,试试毫秒。现在你的寿命范围为 +/- 292 百万 年。

最后,如果您只是 将纳秒级精度保持在几百年以上,<chrono> 也允许您自定义存储:

using dnano = duration<double, nano>;

这给你纳秒存储为 double。如果您的平台支持 128 位整数类型,您也可以使用它:

using big_nano = duration<__int128_t, nano>;

哎呀,如果你为 timespec 编写重载运算符,你甚至可以使用 that 进行存储(虽然我不推荐它)。

您也可以达到比纳秒更精细的精度,但这样做会牺牲范围。例如:

using picoseconds = duration<int64_t, pico>;

这个范围只有 +/- .292 年(几个月)。所以你 do 必须小心。如果您有一个可以提供亚纳秒级精度的源时钟,那么非常适合计时。

查看 this video 了解有关 <chrono> 的更多信息。

为了创建、操作和存储范围大于当前公历有效期的日期,我创建了这个 open-source date library,它使用日历服务扩展了 <chrono> 库。该库将年份存储为带符号的 16 位整数,因此范围为 +/- 32K 年。可以这样使用:

#include "date.h"

int
main()
{
    using namespace std::chrono;
    using namespace date;
    system_clock::time_point now = sys_days{may/30/2017} + 19h + 40min + 10s;
}

更新

在下面的评论中,问题是如何将 "normalize" duration<int32_t, nano> 转换为秒和纳秒(然后将秒添加到 time_point)。

首先,我会谨慎地将纳秒填充到 32 位中。范围略高于 +/- 2 秒。但我是这样区分单位的:

    using ns = duration<int32_t, nano>;
    auto n = ns::max();
    auto s = duration_cast<seconds>(n);
    n -= s;

请注意,这仅在 n 为正时有效。要正确处理负 n,最好的做法是:

    auto n = ns::max();
    auto s = floor<seconds>(n);
    n -= s;

std::floor 是在 C++17 中引入的。如果你早点想要它,你可以从 here or here.

中获取它

我偏爱上面的减法运算,因为我觉得它更具可读性。但这也有效(如果 n 不是负数):

    auto s = duration_cast<seconds>(n);
    n %= 1s;

C++14 引入了1s。在 C++11 中,您将不得不使用 seconds{1} 代替。

一旦你有秒数 (s),你可以将其添加到你的 time_point

std::chrono::nanosecondsstd::chrono::duration<some_t, std::nano> 的类型别名,其中 some_t 是一个至少有 64 位存储空间的有符号整数。这仍然允许至少 292 年的范围和纳秒精度。

值得注意的是,标准提到的唯一具有此类特征的整数类型是 int(|_fast|_least)64_t 系列。

如果您的实现提供了一种类型,您可以自由选择更广泛的类型来表示您的时间。您可以进一步自由地提供一个名称空间,其中包含一堆反映 std::chrono 比率的 typedef,以您的更广泛的类型作为表示。