Я написал класс, который действует как секундомер, используя 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 ответ
Использовать 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
есть конструктор перемещения, но это тоже не повредит.
Также рассмотрите RAII и удалите
go
иstop
функции.— Кейси
Спасибо за обзор, вы уверены в своей первой точке (потеря точности)? Я попробовал ваш пример, но он дает ожидаемый ответ
— Ариан Парех
То, что вы говорите о кругах, имеет смысл, но если я не держал круги, как мне сохранить промежуточное время? Время между двумя кругами
— Ариан Парех
Ах хм, я этого не понимал
std::chrono::hours
на самом деле имеет разрешение в секундах.— Г. Сон
Что касается промежуточного времени: вам нужно только запомнить
time_point
предыдущего круга.— Г. Сон