这个决定是由理论和实际问题驱动的。
理论问题
有一个笑话说,戴着手表的人总是知道现在几点了,但戴着两只手表的人永远不知道。这个笑话里有一点点真实性,它确实影响了这个决定。如果一个应用程序需要知道两个或更多地方的当前时间,并且时钟是有状态的,那么就存在一个问题:如何确保所有代码部分都使用相同的时钟实例来保证所有代码部分都处理“当前时间”的相同定义。
通过使时钟无状态,但允许使用不同类型的多个时钟,类型系统可以帮助程序员确保程序在程序的不同位置使用相同的当前时间定义。而对于那些需要多个时间定义的情况,也可以像不同的类型一样使用。
实际问题
作为更实际的考虑,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://dev59.com/P1sW5IYBdhLWcg3wM0yF#35293183
是的,我遇到过人们想要有状态时钟的情况。本问题的 OP 就提供了这样的例子。但是由于还有“有状态时钟”可以给出静态状态的选项,如果需要其他状态,则使用不同类型;并且由于上述无状态时钟的优点,选择无状态时钟设计更具优势。
更新1
我对客户的说法进行了更多思考:
那么我的有状态时钟不是好代码吗?
我认为,只要人们意识到其限制,有状态时钟就可以使用。不幸的是,由于标准中的一个小错误,这个问题变得比必要的复杂。
从实际角度来看,有状态时钟只有一件事情做不到,而无状态时钟可以做到,这涉及到上面标题为实用性的部分。
您不能实例化需要模板参数为 Clock
TM 的算法(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>;
这在区分与尚未指定时区相关的时间点(考虑类型安全)时非常有用。
如果创建一个没有
static now()
的时钟对于标准来说是可以的,那为什么对你来说不行呢?我能想到的唯一原因就是上面提到的标准中的“小错误”。
C++20规范中的27.6 [time.point]针对
template<class Clock, class Duration> class time_point
提到:
1
Clock
应该满足
Cpp17Clock要求(27.3),或者是类型
local_t
。
我现在认为这太过严格了。程序员应该能够使用具有状态的时钟实例化
time_point
。他们只是不能使用那个
time_point
调用
condition_variable::wait_until
(等等)。但是他们仍然可以获得使用那个
time_point
和由其差异产生的
duration
的所有代数优势。
除了标准规定之外,没有什么好的理由限制这一点。而
local_t
的存在就充分证明了这一点。
1 Clock
应该满足Cpp17Clock要求(27.3),或者是类型local_t
,如果实例化默认模板参数Duration
,则应该有嵌套类型duration
。
更新2
现在有一篇跟踪此问题的论文。
更新3
拟议的更改现在已经包含在C++20标准后的工作草案中:
http://eel.is/c++draft/time.point.general
http://eel.is/c++draft/thread.req.paramname
非常感谢Alexey Dmitriev推动这项工作。