C/C++ Arena

Step 9 of 9

Challenge: program to an interface

In professional code, the most valuable use of virtual functions isn't game units: it's dependency injection. A class depends on an abstract interface instead of a concrete service, and whoever creates it passes in the real thing.

Why that matters: code that calls the system clock, a database or the network directly is hard to test. Tests would have to wait for real time to pass or set up a real server. If the code only talks to an interface, a test can pass in a fake it fully controls.

#include <iostream>

class Clock {
public:
    virtual ~Clock() = default;
    virtual long now() const = 0;
};

class FakeClock : public Clock {        // a test double: time moves only when told
public:
    long now() const override { return t_; }
    void advance(long s) { t_ += s; }
private:
    long t_ = 0;
};

class Coupon {
public:
    Coupon(const Clock& clock, long valid_for) : clock_(clock), expires_(clock.now() + valid_for) {}
    bool valid() const { return clock_.now() < expires_; }
private:
    const Clock& clock_;                // depends on the interface only
    long expires_;
};

int main() {
    FakeClock clock;
    Coupon c(clock, 60);
    std::cout << c.valid();
    clock.advance(59);
    std::cout << c.valid();
    clock.advance(1);
    std::cout << c.valid() << "\n";
}
110

The test checks a 60 second expiry instantly, with exact control over the boundary. In production you'd pass a SystemClock whose now() reads the real time, and Coupon wouldn't change at all.

Designing the rate limiter

Store the times of recent allowed calls in a std::deque<long>, oldest at the front. In allow():

  1. Get now() once.
  2. Pop from the front every time t where now >= t + window_: those calls have left the window.
  3. If fewer than limit_ remain, push now at the back and return true; otherwise return false.

Store the clock as a const Clock& member, as Coupon does. The object that owns the real clock must outlive the limiter.

Your turn: write class RateLimiter that takes a const Clock& and allows at most limit calls to allow() in any window of window seconds. allow() returns true and records the call if under the limit, otherwise false. A call at time t falls out of the window once now() >= t + window.

Previous: Casts, dynamic_cast and slicing