为什么C++标准要求"时钟::现在"函数是"静态的"

Why does the C++ standard require the `Clock::now` function to be `static`?

本文关键字:quot 现在 函数 静态 时钟 标准 C++ 为什么      更新时间:2023-10-16

对于C++11,C++在标准中有一些计时功能。其中一个功能是时钟的标准接口,它基本上允许在时钟的now函数调用时获取时间。

到目前为止一切都很好,但我看不出要求now是静态函数的原因。在托管系统上,标准时钟可以纯粹通过系统调用或通过读取处理器计数器等来实现。但是,这限制了需要维护某些状态的自定义时钟的实现。使用此接口,要么无法实现某些时钟,要么必须使用全局状态。

我遇到的一个问题是基本上将本地时钟同步到我从 NTP 服务器获得的时间。代码看起来像这样:

class sntp_clock
{
public:
sntp_clock() 
: local_time_at_ctor(read_some_cpu_counter())
, sntp_time_at_ctor(read_sntp_time()) {}

sntp_time_t now() const {
return sntp_time_at_ctor + (read_some_cpu_counter() - local_time_at_ctor);
}
/* required types etc */
private:
local_time_t local_time_at_ctor;
sntp_time_t sntp_time_at_ctor;
};

由于我不能在不使状态静止的情况下使now静态,因此该时钟不能满足C++标准时钟的要求。但每个 NTP 服务器都有单独的状态。

此外,出于效率原因,我可能不想启动存在时钟实例的 cpu 计数器,但同样,由于now是静态的,我无法确切知道何时开始时钟,或何时停止它。

我的问题是为什么时钟有静态now要求?

注意:当前标准草案要求now为静态:http://eel.is/c++draft/time.clock.req#tab:time.clock

Boost.Chrono 文档有相同的要求:https://www.boost.org/doc/libs/1_63_0/doc/html/chrono/reference.html#chrono.reference.cpp0x.clock

理论和实践问题推动了这一决定。

理论

有一个笑话说,一个有手表的人总是知道现在几点,但一个有两块手表的人永远不会知道。 这个笑话有一点点道理,它确实影响了决定。 如果应用程序需要知道两个或更多位置的当前时间,并且如果时钟是有状态的,则在确保在所有位置使用相同的时钟实例以确保代码的所有部分都处理"当前时间"的相同定义时存在问题。

通过使时钟无状态,但允许具有不同类型的多个时钟,类型系统可以帮助程序员确保程序在程序的不同位置使用相同的当前时间定义。 然而,在那些需要对时间有多种定义的情况下,这也是可用的,就像不同的类型一样。

实用的

作为更实际的考虑,chrono::clock代码的第一个客户端是chrono本身的。 我们不得不吃自己的狗粮。 以condition_variable::wait_until的实现为例:

https://github.com/llvm-mirror/libcxx/blob/master/include/__mutex_base#L377-L385

template <class _Clock, class _Duration>
cv_status
condition_variable::wait_until(unique_lock<mutex>& __lk,
const chrono::time_point<_Clock, _Duration>& __t)
{
using namespace chrono;
wait_for(__lk, __t - _Clock::now());
return _Clock::now() < __t ? cv_status::no_timeout : cv_status::timeout;
}

这里一个函数采用一个泛型time_point,算法需要找到与该time_point相关的当前时间。 通过将Clock的类型打包到time_point的类型中具有static now(),代码编写起来非常干净,并且具有非常干净的界面。 然而,它足够通用,此代码将适用于任何用户编写的自定义时钟:而不仅仅是 std 指定的时钟。

如果时钟是有状态的,那么要么:

  1. condition_variable::wait_until无法获取当前时间,或
  2. 客户端还必须传入测量time_point的时钟。

以上两个选择对我来说似乎都不是可口的。

请注意,condition_variable::wait_until不是特例,而只是众多此类算法中的一个示例。 事实上,我认为不仅标准实现者会编写这样的算法,而且公众也会编写。 以下是后者的示例:

https://stackoverflow.com/a/35293183/576911


是的,我遇到过人们想要有状态时钟的情况。 这个问题的OP提供了这样一个例子。 但是由于可以选择"有状态时钟"仍然可以被赋予静态状态,如果您需要其他状态,请使用不同的类型; 并且由于上述无状态时钟的优点; 选择优势在于无状态时钟设计。

<小时 />

更新1

我一直在考虑客户说:

那么我的有状态时钟不是好代码吗?

我认为有状态的时钟是好的,只要人们意识到它的局限性。 不幸的是,由于标准中的一个小错误,我认为这是一个比需要的更复杂的问题。

从实用的角度来看,只有一件事你不能用有状态时钟做,你可以用无状态时钟做,它可以追溯到上面标题为"实用">的部分。

您不能实例化需要模板参数为ClockTM的算法(std 或其他)。

例如,如果您有一个基于有状态时钟的time_point,则不能使用该time_point调用condition_variable::wait_until。 如果你不想这样做,这并不意味着你的有状态时钟不好。 如果您的有状态时钟服务于您的目的,那么这很好

在 C++20 中,甚至有一个时钟示例不符合所有 C++11-17 时钟要求:

struct local_t {};

是的,那是(有点)一个时钟。 但它几乎什么也没做。 它用于创建一个没有关联now()time_point族:

template<class Duration>
using local_time  = time_point<local_t, Duration>;

事实证明,在区分 UTC 时间点和与尚未指定的时区关联的时间点时,这非常有用(想想类型安全)。

因此,如果创建一个没有static now()的时钟对于标准来说是可以的,为什么不适合你呢?! 我能想到的唯一原因是我上面引用的标准中的"小错误"。

27.6 [time.point] 在 C++20 规范中这样说template<class Clock, class Duration> class time_point

1Clock应满足Cpp17Clock要求 (27.3) 或属于local_t型。

我现在认为这过于严格。 程序员应该能够使用有状态时钟实例化time_point。 他们只是不能用那个time_point打电话给condition_variable::wait_until(等人)。 但是他们仍然可以获得使用该time_point的所有代数好处以及由其差异引起的duration

除了标准这么说之外,没有充分的理由进行这种限制。 而local_t的存在也不符合Cpp17Clock的要求,这几乎证明了这一点。

1Clock

满足Cpp17Clock要求 (27.3),或者是具有嵌套类型local_tduration的类型(如果实例化默认模板参数Duration

更新 2

现在有一篇论文跟踪这个问题。

更新 3

提议的更改现在在 C++20 之后的标准工作草案中:

http://eel.is/c++draft/time.point.general

http://eel.is/c++draft/thread.req.paramname

非常感谢阿列克谢·德米特里耶夫推动这项工作。

相关文章: