AI 24/25 Project Software
Documentation for the AI 24/25 course programming project software
Loading...
Searching...
No Matches
subscriber.h
1#ifndef ALGORITHMS_SUBSCRIBER_H
2#define ALGORITHMS_SUBSCRIBER_H
3
4#include <cassert>
5#include <unordered_set>
6
7/*
8 The classes in this file allow objects of one class to react to the
9 destruction of objects of another class.
10
11 If a class T1 wants to react to the destruction of objects of a class T2,
12 derive T2 from SubscriberService<T2>, derive T1 from Subscriber<T1> and
13 override the method notify_service_destroyed in T1.
14
15 Example:
16
17 class Star : public SubscriberService<Star> {
18 string name;
19 ...
20 }
21
22 vector<const Star *> galaxy;
23
24 class Astronomer : public Subscriber<Star> {
25 Astronomer() {
26 for (const Star *star : galaxy) {
27 star.subscribe(this);
28 }
29 }
30
31 virtual void notify_service_destroyed(const Star *star) {
32 cout << star->name << " is going supernova!\n";
33 }
34 }
35*/
36
37
38namespace subscriber {
39template<typename T>
40class SubscriberService;
41
42/*
43 A Subscriber can subscribe to a SubscriberService and is notified if that
44 service is destroyed. The template parameter T should be the class of the
45 SubscriberService (see usage example above).
46*/
47template<typename T>
48class Subscriber {
49 friend class SubscriberService<T>;
50 std::unordered_set<const SubscriberService<T> *> services;
51 virtual void notify_service_destroyed(const T *) = 0;
52public:
53 virtual ~Subscriber() {
54 /*
55 We have to copy the services because unsubscribing erases the
56 current service during the iteration.
57 */
58 std::unordered_set<const SubscriberService<T> *> services_copy(services);
59 for (const SubscriberService<T> *service : services_copy) {
60 service->unsubscribe(this);
61 }
62 }
63};
64
65template<typename T>
66class SubscriberService {
67 /*
68 We make the set of subscribers mutable, which means that it is possible
69 to subscribe to `const` objects. This can be justified by arguing that
70 subscribing to an object is not conceptually a mutation of the object,
71 and the set of subscribers is only incidentally (for implementation
72 efficiency) stored along with the object. Of course it is possible to
73 argue the other way, too. We made this design decision because being able
74 to subscribe to const objects is very useful in the planner.
75 */
76 mutable std::unordered_set<Subscriber<T> *> subscribers;
77public:
78 virtual ~SubscriberService() {
79 /*
80 We have to copy the subscribers because unsubscribing erases the
81 current subscriber during the iteration.
82 */
83 std::unordered_set<Subscriber<T> *> subscribers_copy(subscribers);
84 for (Subscriber<T> *subscriber : subscribers_copy) {
85 subscriber->notify_service_destroyed(static_cast<T *>(this));
86 unsubscribe(subscriber);
87 }
88 }
89
90 void subscribe(Subscriber<T> *subscriber) const {
91 assert(subscribers.find(subscriber) == subscribers.end());
92 subscribers.insert(subscriber);
93 assert(subscriber->services.find(this) == subscriber->services.end());
94 subscriber->services.insert(this);
95 }
96
97 void unsubscribe(Subscriber<T> *subscriber) const {
98 assert(subscribers.find(subscriber) != subscribers.end());
99 subscribers.erase(subscriber);
100 assert(subscriber->services.find(this) != subscriber->services.end());
101 subscriber->services.erase(this);
102 }
103};
104}
105#endif