为什么 C++ 标准要求 `Clock::now` 函数是 `static`?

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

对于 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++ 标准中 Clock 的要求。但是每个 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

做出这一决定的原因既有理论方面的,也有实际方面的考虑。

理论

有个笑话,一个人有一只表,总能知道现在几点,而有两只表的人,却永远不知道。那个笑话有一点点道理,它确实影响了决定。如果应用程序需要知道两个或更多地方的当前时间,并且时钟是有状态的,那么确保在所有地方使用相同的 instance 时钟是一个问题确保代码的所有部分都处理相同的“当前时间”定义。

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

实用

出于实际考虑,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(),代码写起来非常干净,并且有一个非常干净的界面。然而,它足够通用,因此此代码可以与 any user-written 自定义时钟一起使用:而不仅仅是 std-specified 时钟。

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

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

以上两种选择都不适合我。

请注意,condition_variable::wait_until 不是特例,而只是许多此类算法中的一个示例。事实上,我假设不仅标准实现者会编写这样的算法,而且一般的 public 也会。这是后者的一个例子:

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


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


更新 1

我一直在考虑客户说:

So is my stateful clock not good code?

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

从实用的角度来看,只有一件事是有状态时钟无法做到的,而无状态时钟却可以做到,这可以追溯到上面标题为 实用.

You can not instantiate an algorithm (std or otherwise) that requires a template parameter to be a ClockTM.

例如,如果您有一个基于有状态时钟的 time_point,则不能使用该 time_point 来调用 condition_variable::wait_until。如果您无论如何都不想这样做,那并不意味着您的有状态时钟不好。如果您的状态时钟符合您的目的,那么那就是 fine.

在 C++20 中,甚至还有一个不满足所有 C++11-17 时钟要求的时钟示例:

struct local_t {};

是的,那是(某种)时钟。但它几乎没有做任何事情。它用于创建没有关联的 time_point 系列 now():

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

事实证明,这在区分 UTC 时间点和与 as-yet-unspecified 时区关联的时间点(考虑类型安全)时非常有用。

因此,如果创建一个没有 static now() 的时钟符合标准,那为什么不适合你呢?!我能想到的唯一原因是我上面提到的标准中的“小错误”。

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

1 Clock shall either satisfy the Cpp17Clock requirements (27.3) or be the type local_t.

我现在认为这是过度限制。程序员应该能够使用状态时钟实例化 time_point。他们只是不能用 time_point 调用 condition_variable::wait_until(等)。但是他们仍然可以获得使用 time_pointduration 的所有代数优势。

除了标准说法外,没有充分的理由进行此限制。 local_t 的存在也不符合 Cpp17Clock 的要求,这就很好地证明了这一点。

1 Clock shall either satisfy the Cpp17Clock requirements (27.3) or be the type local_t have the nested type duration if the instantiation defaults the template parameter Duration.

更新 2

现在有 paper tracking this issue.

更新 3

proposed changes 现在在 C++20 之后的标准工作草案中:

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

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

非常感谢 Alexey Dmitriev喜欢这项工作。