Skip to content

Mini Project 6 — Stopwatch

Difficulty: Intermediate · Estimated effort: 3–4 hours · Prerequisites: Module 3 — Constructors and Destructors, Module 10 — Copying and Resource Management, Module 11 — RAII and Smart Pointers · Combines: object lifetime, value semantics decided on purpose, RAII, <chrono>

No complete solution is provided. The interesting part of this project is not the timing call — it is deciding what copying a running stopwatch means.


1. Scenario

You are timing the exercises in this course to find out which ones you are slow at. Timing things by hand is a distraction, so you want a small Stopwatch type for the mid-project measurements, and a ScopedTimer that prints how long a block took without needing a matching stop call.


2. Problem statement

Build a Stopwatch that starts, stops, resumes and reports elapsed time, and a ScopedTimer that measures a scope automatically. Decide, deliberately, what copying and moving these types mean, and make the compiler enforce that decision.


3. User requirements

  • [ ] Start and stop the watch, and read the elapsed time between.
  • [ ] Stop and start again to accumulate a total across several runs.
  • [ ] See the elapsed time printed in a readable unit.
  • [ ] Time a block of code with one line of setup and no matching teardown.
  • [ ] Have the watch behave predictably when it is copied or moved.

4. Functional requirements

  • [ ] Stopwatch with start(), stop(), reset(), elapsed(), and running().
  • [ ] elapsed() returns a duration; printing picks the unit (us, ms, s).
  • [ ] Accumulated time survives a stop/start pair.
  • [ ] Calling stop() on a stopped watch, or start() on a running one, is handled by a documented rule — not by undefined behaviour.
  • [ ] ScopedTimer starts in its constructor, measures in its destructor, and takes a label.
  • [ ] Copy and move behaviour of both types is explicit: either implemented, or deleted.

5. Non-functional requirements

  • [ ] One header and one source file; ScopedTimer may live in the same pair.
  • [ ] Uses std::chrono::steady_clock for durations; say why not system_clock.
  • [ ] Builds warning-free with g++ -std=c++17 -Wall -Wextra -Werror.
  • [ ] No global timer state; two independent watches do not interfere.
  • [ ] The rule for copy and move is the same in the code and in the documentation.

6. Suggested features

  • A lap() that records a split and keeps the watch running.
  • A Statistics helper over a vector of durations: count, total, mean, minimum, maximum.
  • Formatted output lined up in columns.
  • A ScopedTimer that reports to a std::ostream& supplied at construction.

7. OOP concepts involved

Concept Where it appears
Constructors / destructors ScopedTimer is pure RAII: the destructor does the work
Rule of Zero If the members are values, the compiler's defaults may already be right
Rule of Five If you write one of copy/move/destroy, decide all five
Value semantics Copying a running watch is a design decision, not a default
Encapsulation The running flag and accumulated duration cannot contradict each other
Non-copyability = delete where a copy would be meaningless

8. Suggested design considerations

Decide what a copy means, and enforce it. Four defensible answers: a copy is a watch with the same totals and the same running state (two watches now time the same thing, which is probably wrong); a copy is stopped (surprising); copying is deleted (clear); or the type is made movable-only. Pick one, write it down, and make the compiler agree.

One clock, one call site. Read the clock in exactly one private helper. Scattering now() calls through the class is how a stopwatch acquires an off-by-one-frame bug.

The accumulated-duration invariant. elapsed() must be correct whether the watch is running or stopped. Put the branch in one place and note the invariant.

A destructor that will run during an exception. ScopedTimer measures correctly when the scope is left by a thrown exception — verify that, because it is the reason RAII beats manual timing.


9. Minimum viable version

Stopwatch with the five operations and printing; ScopedTimer measuring one block; a main timing a deliberately slow loop and demonstrating accumulated time across two runs.


10. Intermediate extension

Add move semantics, or state with a static_assert-style comment why you did not need them. Then time a scope that exits early with return and one that exits by throwing, and confirm both are measured.


11. Advanced extension

Add laps and a statistics summary, using the STL rather than hand-written loops for the aggregates. Then measure a std::vector growing and a std::vector pre-reserved, printed as a small table — this is the kind of measurement Module 14 asks you to justify with evidence rather than habit.


12. Testing checklist

  • [ ] A watch that was never started reports zero elapsed
  • [ ] start(); stop(); reports a positive interval, and the same after ten repetitions
  • [ ] Two stop/start cycles accumulate: the total is at least the sum of the two intervals
  • [ ] stop() on a stopped watch does not change the elapsed time
  • [ ] reset() returns the watch to the never-started state
  • [ ] Two independent watches do not interfere
  • [ ] A ScopedTimer in a scope left by throw still measures (verified by output)
  • [ ] A ScopedTimer in a scope left by early return still measures

13. Documentation checklist

  • [ ] The copy and move rule, stated where a user of the class will read it
  • [ ] Why steady_clock and not system_clock
  • [ ] The invariant about accumulated time while running
  • [ ] The repeat-start()/stop() rule
  • [ ] The measured comparison from the advanced extension, if you did it

14. Self-evaluation rubric

Criterion Excellent looks like Weight
Lifetime design Copy and move rules chosen, documented and compiler-enforced 30%
Correctness Elapsed time right when running, stopped, and after several cycles 25%
RAII ScopedTimer measures on every exit path, including exceptions 20%
Interface Units are explicit; the printing is readable and honest 10%
Testing Both exit paths and the boundary cases are actually tested 10%
Documentation The four decisions above are written down 5%

Related: Mini projects · Project roadmap · All projects · Module 10 — Copying and Resource Management