Класс Stop Watch в C ++ с использованием хронографа

Я написал класс, который действует как секундомер, используя C ++ 11 std::chrono библиотека. Я написал это, потому что мне трудно правильно протестировать мой код cpp, этот класс просто сочетает в себе функциональность chrono и дает ему приятный интерфейс.

У меня два класса, stopwatch.h и lapwatch.h. Второй имеет функцию секундомера, но также имеет функцию круга, которую вы можете использовать для нескольких интервалов.

stopwatch.h

#ifndef STOPWATCH_H_
#define STOPWATCH_H_
#include <chrono>

template<typename duration = std::chrono::milliseconds>
class StopWatch
{
public:
  using clock      = std::chrono::steady_clock;
  using time_point = std::chrono::time_point<clock, duration>;

  StopWatch() = default;
protected:
  enum class State : unsigned char;

  static constexpr time_point current_time() noexcept
  {
    return std::chrono::time_point_cast<duration>(clock::now());
  }
public:
  virtual void reset() noexcept
  {
    clock_state = State::idle;
  }

  time_point go() noexcept
  {
    if (clock_state != State::stopped)
      start_point = current_time();

    clock_state = State::running;
    return start_point;
  }

  void stop() noexcept
  {
    if (clock_state == State::running)
    {
      stop_point = current_time();
      clock_state = State::stopped;
    }
  }

  duration elapsed_time() const noexcept
  {
    switch (clock_state)
    {
    case State::idle:
      return std::chrono::duration_cast<duration>(clock::duration::zero());

    case State::running:
      return std::chrono::duration_cast<duration>(current_time() - start_point);

    case State::stopped:
      return std::chrono::duration_cast<duration>(stop_point - start_point);

    default:
      return std::chrono::duration_cast<duration>(clock::duration::zero());
      break;
    }
  }

protected:
  enum class State : unsigned char { idle, running, stopped };

  time_point start_point;
  time_point stop_point;
  State      clock_state = State::idle;
};

lapwatch.h

  
#ifndef LAPWATCH_H_
#define LAPWATCH_H_
#include "stopwatch.h"
#include <vector>

template<typename duration = std::chrono::milliseconds>
class LapWatch : public StopWatch<duration>
{
  using clock      = typename StopWatch<duration>::clock;
  using time_point = typename StopWatch<duration>::clock;
  using StopWatch<duration>::State;
  
public:
  LapWatch() = default;
  struct Lap
  {
    duration total_time;
    duration split_time;
  };

  void lap() 
  {
    if (this->clock_state != StopWatch<duration>::State::running)
      return;

    Lap current;
    if (laps.size() == 0)
      current.total_time = current.split_time = StopWatch<duration>::elapsed_time();
    else
    {
      current.total_time = StopWatch<duration>::elapsed_time();
      current.split_time = (current.total_time - laps[laps.size() - 1].total_time);
    }
    laps.push_back(static_cast<Lap&&>(current));
  }

  void reset() noexcept
  {
    laps.clear();
  }

  std::vector<Lap> laps;
};
#endif // !LAPWATCH_H_

пример использования

#include "lapwatch.h"

int main()
{
  LapWatch<> watch;
  
  watch.go() // start the timer
  // Some time passes
  
  watch.lap() // Adds the total and split time, but doesn't stop it
  
  // Some more time passes
  watch.lap() // Adds another lap

  watch.stop() // stop the watch now
  
  // Laps are added to a vector in watch.laps
  
  for(auto const& lap : watch.laps) {
    lap.total_time // Total time passed since the start
    lap.split_time // Total time passed since the previous lap
  }
}

Обеспокоенность

  • Это хорошая идея? Вы бы использовали это?

  • Это красивый дизайн?


У меня есть гитхаб хранилище для него тоже, если кто-то предпочитает читать код таким образом.

1 ответ
1

Использовать clockродное разрешение везде

Если вы примените точки времени и длительности к чему-то с разрешением, отличным от разрешения clock, вы рискуете потерять точность. Итак, для всего внутреннего состояния ваших классов я бы определенно сохранил все как clock::duration и clock::time_point типы. Вы можете подумать о сохранении параметра шаблона, чтобы гарантировать, что типы возвращаемых общедоступных функций-членов имеют запрошенное разрешение по времени, но это просто упрощает вызывающему объекту возможность совершить ту же ошибку.

Чтобы дать пример того, где что-то может пойти не так, предположим, я пишу:

StopWatch<std::chrono::seconds> watch;
watch.go(); // done at 11:59:59.999
...
watch.stop(); // done at 12:00:00.000
auto elapsed = watch.elapsed_time();

С вашей реализацией elapsed будет равно одной секунде, даже если прошла всего одна миллисекунда.

Если проблема просто в удобстве, вызывающему абоненту не нужно звонить std::chrono::duration_cast<>() сами, я бы удалил параметр шаблона из class, и сделайте функцию-член elapsed_time() вместо этого шаблон, например:

template <typename duration = clock::duration>
duration elapsed_time() const noexcept
{
    ...
}

current_time() не должно быть constexpr

Если вы отметите функцию constexpr, вы обещаете, что эта функция может быть оценена во время компиляции. Но здесь это не имеет смысла.

Не рекомендуется сохранять результаты кругов в LapWatch

Имея struct Lap и функция отметки пройденного круга — это хорошо. Однако, сделав lap() сохранить результаты в переменной-члене, теперь вы дали LapWatch ответственность за хранение кругов. Иногда это может быть именно то, что вы хотите, но, возможно, вызывающий абонент хочет сделать что-то совершенно другое. Может быть, предыдущие круги не нужно сохранять, или их нужно сохранить по-другому. я бы сделал lap() вернуть Lap вместо этого, и пусть вызывающая сторона решит, хочет ли он добавить их в какой-либо контейнер. Так:

Lap lap() {
    Lap current;
    ...
    return current;
}

И тогда звонящий может написать:

LapWatch<> watch;
std:vector<Lap> laps;
laps.push_back(watch.lap());

Использовать std::move() вместо static_cast<Lap&&>()

Если вы действительно хотите переместить объект, используйте std::move(), он короче и менее подвержен ошибкам.

Обратите внимание, что ни актеры, ни std::move() будет делать что угодно в этом случае, поскольку ни Lap ни std::chrono::duration есть конструктор перемещения, но это тоже не повредит.

  • 3

    Также рассмотрите RAII и удалите go и stop функции.

    — Кейси

  • Спасибо за обзор, вы уверены в своей первой точке (потеря точности)? Я попробовал ваш пример, но он дает ожидаемый ответ

    — Ариан Парех

  • То, что вы говорите о кругах, имеет смысл, но если я не держал круги, как мне сохранить промежуточное время? Время между двумя кругами

    — Ариан Парех

  • Ах хм, я этого не понимал std::chrono::hours на самом деле имеет разрешение в секундах.

    — Г. Сон

  • Что касается промежуточного времени: вам нужно только запомнить time_point предыдущего круга.

    — Г. Сон

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *