【问题标题】:Why does the C++ standard require the `Clock::now` function to be `static`?为什么 C++ 标准要求`Clock::now` 函数是`static`?
【发布时间】:2019-10-17 09:53:24
【问题描述】:

对于 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

【问题讨论】:

  • @tadman 我认为问题在于问为什么委员会选择以这种方式编写标准。
  • @tadman,我想知道这个决定背后的原因。 技术上所有时钟都有一些状态。只是操作系统正在为您维护该状态。在我们的嵌入式操作系统上,我必须实现时钟,并且我想让它们与 C++ 标准兼容。
  • 因为now 函数不使用任何现有的时钟对象来计算“现在”时间?因此它不需要非静态成员函数。它甚至可以是一个非成员函数。
  • @RobertHarvey,我确定 Howard Hinnant 在这里。此外,如果我们得到答案,我想把它放在这里,让其他可能想知道同样问题的人看到。
  • @FatihBAKIR:“事实上,我想知道是否可以在未来的标准中删除限制。”不,它不能。所有期望Clock 编写的模板代码都期望它具有静态now 函数。因此会打破一个非静态的。

标签: c++ language-lawyer chrono


【解决方案1】:

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

理论

有一个笑话说,有手表的人总能知道现在几点,但有两块手表的人却不知道。那个笑话有一点点道理,它确实影响了决定。如果应用程序需要知道两个或多个地方的当前时间,并且如果时钟是有状态的,那么确保在所有地方都使用相同的时钟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(),代码编写起来非常干净,并且具有非常干净的界面。然而,这段代码足够通用,可以与任何用户编写的自定义时钟一起使用:不仅仅是标准指定的时钟。

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

  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_points 系列:

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

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

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

C++20 规范中的 27.6 [time.point] 关于template&lt;class Clock, class Duration&gt; class time_point

1 Clock 应满足 Cpp17Clock 要求 (27.3) 或为 local_t 类型。

我现在认为这是过于严格了。程序员应该能够使用有状态时钟来实例化time_point。他们只是不能用time_point 打电话给condition_variable::wait_until(等人)。但他们仍然可以获得使用 time_pointdurations 所带来的所有代数优势。

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

1 Clock满足 Cpp17Clock 要求 (27.3) 或为 local_t 类型 如果实例化,则具有嵌套类型 duration默认模板参数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 推动这项工作。

【讨论】:

  • 很高兴您看到并回答了这个问题。我只是在阅读N2661,看看它是否在那里并得出了相同的结论。感谢您的... 时间 ;)
  • 我希望我能有远见(和时间)将这个理由放入N2661。这段时间对我来说很忙,几乎没有时间写下需要写下的所有内容。
  • 你没有提到这一点,我认为这是静态函数的一个被低估的特性,它允许将接口泛化为有状态对象并且相同的接口可以工作,例如template&lt;class FancyClock&gt; void f(FancyClock c){... c.now();}(适用于静态成员函数now()c 可以是有状态的或没有状态的)。与选择(或更确切地说,引用实现)静态库函数有什么关系吗?
  • 这很好。我已经知道该语言的这一特性。但是我在 time_points 或时钟的通用函数中没有这种语言特性的特定激励用例。我见过为有状态时钟编写的代码,由于这种语言特性,这些代码可能适用于无状态时钟。但是这样的代码并不是真正的通用。我同意这是该语言的一个被低估的功能(能够使用obj. 语法调用静态成员函数)。
猜你喜欢
  • 2014-08-13
  • 2017-02-17
  • 1970-01-01
  • 2016-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-03
  • 2015-05-31
相关资源
最近更新 更多