C/C++ Arena

Step 3 of 7

Observers with weak_ptr

In the Observer pattern, a subject (a stock ticker, a button, a scoreboard) notifies a list of listeners when something happens. The hard part in C++ is lifetime: if a listener is destroyed while the subject still holds a raw pointer to it, the next notification uses a dangling pointer and the program crashes, or worse.

Storing std::weak_ptr solves it. The subject doesn't keep listeners alive, and lock() tells it whether each one still exists.

#include <iostream>
#include <memory>
#include <string>
#include <vector>

struct Screen {
    std::string name;
    void show(double price) const { std::cout << name << " shows " << price << "\n"; }
};

class Ticker {
public:
    void attach(const std::shared_ptr<Screen>& s) { screens_.push_back(s); }
    void update(double price) {
        for (const auto& w : screens_) {
            if (auto s = w.lock()) s->show(price);      // alive: notify
        }
    }
    std::size_t attached() const { return screens_.size(); }

private:
    std::vector<std::weak_ptr<Screen>> screens_;
};

int main() {
    Ticker t;
    auto lobby = std::make_shared<Screen>(Screen{"lobby"});
    t.attach(lobby);
    {
        auto phone = std::make_shared<Screen>(Screen{"phone"});
        t.attach(phone);
        t.update(101.5);
    }                                 // phone is destroyed here
    t.update(99.0);                   // skipped safely: lock() returns empty
    std::cout << t.attached() << " entries still stored\n";
}
lobby shows 101.5
phone shows 101.5
lobby shows 99
2 entries still stored

How it works

The leftover entry

Notice the ticker still stores 2 entries after phone died. The dead weak_ptr is harmless, but over time a long-running subject would collect thousands of them. Your task fixes this: remove expired entries while publishing. std::erase_if(listeners_, [](const auto& w) { return w.expired(); }) does it in one call, or you can build a new vector of the live ones as you notify.

Your task

Implement Scoreboard::subscribe and Scoreboard::publish. publish notifies every living listener in subscription order, removes expired ones, and returns how many were notified.

Challenge 2

Unsubscribing by hand is easy to forget, so hand out an RAII Subscription. Signal::connect(fn) stores the callback under a new id and returns a Subscription; destroying the subscription removes the callback. emit(v) calls every connected callback, and size() counts them. Subscription is move-only.

Challenge 3

Listeners can now subscribe to one team or to all of them (an empty team string). publish(team, score) notifies the living listeners interested in that team, in subscription order, removes expired ones, and returns how many it notified. The scoreboard must not keep listeners alive.

Previous: A factory registry Next: Pimpl