Почему стандарт C ++ требует, чтобы функция `Clock :: now` была` static`? - PullRequest
6 голосов
/ 31 мая 2019

С 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 ++.

Кроме того, по соображениям эффективности я не хочу запускать счетчик ЦП, если существует экземпляр часов, но опять же, поскольку 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

1 Ответ

13 голосов
/ 31 мая 2019

Существовали как теоретические, так и практические проблемы, которые привели к принятию этого решения.

Теоретическая

Есть шутка, что человек с часами всегда знает, который час, а человек с двумя часами никогда не знает. В этой шутке есть доля правды, и это действительно повлияло на решение. Если приложению необходимо знать текущее время в двух или более местах и ​​если часы имеют состояние, то существует проблема в том, чтобы один и тот же экземпляр часов использовался во всех местах, чтобы гарантировать, что все части кода имеют одно и то же определение «текущего времени».

Делая часы без сохранения состояния, но допуская несколько часов с разными типами, система типов может помочь программисту гарантировать, что программа использует одно и то же определение текущего времени в разных местах программы. И все же в тех случаях, когда требуется несколько определений времени, это также доступно, как отдельные типы.

Практическое

В качестве более практического соображения самыми первыми клиентами кода 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


Да, я сталкивался со случаями, когда людям нужны часы с состоянием ОП этого вопроса предлагает такой пример. Но так как есть опция, что «часам с состоянием» все еще может быть присвоено статическое состояние, и если вам нужно другое состояние, используйте другой тип; И из-за преимуществ часов без гражданства, указанных выше; Выбор был сделан, что преимущество заключалось в дизайне часов без сохранения состояния.


Обновление

Я больше думал о клиенте, который говорит:

Так мои часы с состоянием не являются хорошим кодом?

Я думаю, что часы с состоянием - это хорошо, если только вы понимаете ограничения, которые на них установлены. И, к сожалению, я считаю, что это более сложная проблема, чем необходимо из-за незначительной ошибки в стандарте.

С практической точки зрения, есть только одна вещь, которую вы не можете сделать с часами с состоянием, которую вы можете сделать с часами без состояния, и это восходит к разделу выше под названием The Practical .

Нельзя создать экземпляр алгоритма (стандартного или иного), который требует, чтобы параметр шаблона был Clock TM .

Например, если у вас time_point на основе часов с состоянием, вы не можете использовать time_point для вызова condition_variable::wait_until. Если вы все равно не хотите этого делать, это не значит, что ваши часы с состоянием плохие. Если ваши часы с состоянием служат вашим целям, то это нормально.

В проекте C ++ 20 есть даже пример часов, которые не отвечают всем требованиям часов C ++ 11-17:

struct local_t {};

Да, это (вроде) часы. Но это почти ничего не делает. Он используется для создания семейства time_point s, с которым не связано now():

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:

1 Clock должен либо удовлетворять требованиям Cpp17Clock (27.3), либо быть типом local_t.

Теперь я считаю, что это слишком ограничительно. Программисты должны иметь возможность создавать time_point с часами с отслеживанием состояния. Они просто не могут позвонить condition_variable::wait_until (и др.) С этим time_point. Но они все еще могут получить все алгебраические преимущества от использования этих time_point и duration s, вытекающих из его различий.

Нет веских оснований для этого ограничения, за исключением стандартного изречения. И само существование local_t, которое также не соответствует требованиям Cpp17Clock , в значительной степени подтверждает это.

1 Clock должен либо удовлетворять требованиям Cpp17Clock (27.3), либо быть типом local_t иметь вложенный тип duration, если экземпляр по умолчанию имеет значение параметр шаблона Duration.

...