Merge lp:~thomas-voss/properties-cpp/cleanup into lp:properties-cpp
- cleanup
- Merge into trunk
Proposed by
Thomas Voß
Status: | Merged |
---|---|
Merged at revision: | 4 |
Proposed branch: | lp:~thomas-voss/properties-cpp/cleanup |
Merge into: | lp:properties-cpp |
Diff against target: |
456 lines (+123/-87) 6 files modified
README.md (+6/-3) include/com/ubuntu/connection.h (+30/-7) include/com/ubuntu/property.h (+1/-26) include/com/ubuntu/signal.h (+64/-27) tests/properties_test.cpp (+1/-18) tests/signals_test.cpp (+21/-6) |
To merge this branch: | bzr merge lp:~thomas-voss/properties-cpp/cleanup |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jussi Pakkanen (community) | Approve | ||
Review via email: mp+196829@code.launchpad.net |
Commit message
Adjusted the introductory code example to always drain the event queue.
Added a test to make sure that a signal resets all registered slots on destruction.
Documented the usage of a shared private state.
Description of the change
Adjusted the introductory code example to always drain the event queue.
Added a test to make sure that a signal resets all registered slots on destruction.
Documented the usage of a shared private state.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'README.md' |
2 | --- README.md 2013-11-13 10:41:06 +0000 |
3 | +++ README.md 2013-11-27 08:46:56 +0000 |
4 | @@ -72,8 +72,11 @@ |
5 | std::chrono::milliseconds{500}, |
6 | [this]() { return handlers.size() > 0; }); |
7 | |
8 | - handlers.front()(); |
9 | - handlers.pop(); |
10 | + while (handlers.size() > 0) |
11 | + { |
12 | + handlers.front()(); |
13 | + handlers.pop(); |
14 | + } |
15 | } |
16 | } |
17 | |
18 | @@ -131,4 +134,4 @@ |
19 | if (dispatcher_thread.joinable()) |
20 | dispatcher_thread.join(); |
21 | } |
22 | -~~~~~~~~~~~~~ |
23 | \ No newline at end of file |
24 | +~~~~~~~~~~~~~ |
25 | |
26 | === modified file 'include/com/ubuntu/connection.h' |
27 | --- include/com/ubuntu/connection.h 2013-11-13 09:19:50 +0000 |
28 | +++ include/com/ubuntu/connection.h 2013-11-27 08:46:56 +0000 |
29 | @@ -31,8 +31,7 @@ |
30 | */ |
31 | class Connection |
32 | { |
33 | -public: |
34 | - typedef std::function<void()> Disconnector; |
35 | +public: |
36 | typedef std::function<void(const std::function<void()>&)> Dispatcher; |
37 | |
38 | /** |
39 | @@ -58,10 +57,12 @@ |
40 | */ |
41 | inline void dispatch_via(const Dispatcher& dispatcher) |
42 | { |
43 | - d->dispatcher_installer(dispatcher); |
44 | + if (d->dispatcher_installer) |
45 | + d->dispatcher_installer(dispatcher); |
46 | } |
47 | |
48 | private: |
49 | + typedef std::function<void()> Disconnector; |
50 | typedef std::function<void(const Dispatcher&)> DispatcherInstaller; |
51 | |
52 | template<typename ... Arguments> friend class Signal; |
53 | @@ -72,6 +73,11 @@ |
54 | { |
55 | } |
56 | |
57 | + inline void reset() |
58 | + { |
59 | + d->reset(); |
60 | + } |
61 | + |
62 | struct Private |
63 | { |
64 | Private(const Connection::Disconnector& disconnector, |
65 | @@ -81,23 +87,40 @@ |
66 | { |
67 | } |
68 | |
69 | + inline void reset() |
70 | + { |
71 | + std::lock_guard<std::mutex> lg(guard); |
72 | + reset_locked(); |
73 | + } |
74 | + |
75 | + inline void reset_locked() |
76 | + { |
77 | + static const Connection::Disconnector empty_disconnector{}; |
78 | + static const Connection::DispatcherInstaller empty_dispatcher_installer{}; |
79 | + |
80 | + disconnector = empty_disconnector; |
81 | + dispatcher_installer = empty_dispatcher_installer; |
82 | + } |
83 | + |
84 | inline void disconnect() |
85 | { |
86 | static const Connection::Disconnector empty_disconnector{}; |
87 | |
88 | std::lock_guard<std::mutex> lg(guard); |
89 | |
90 | - if (!disconnector) |
91 | - return; |
92 | + if (disconnector) |
93 | + disconnector(); |
94 | |
95 | - disconnector(); |
96 | - disconnector = empty_disconnector; |
97 | + reset_locked(); |
98 | } |
99 | |
100 | std::mutex guard; |
101 | Connection::Disconnector disconnector; |
102 | Connection::DispatcherInstaller dispatcher_installer; |
103 | }; |
104 | + |
105 | + // The whole class is implicitly shared and we thus forward our complete |
106 | + // shared state to a private structure that is lifetime-managed by a shared_ptr. |
107 | std::shared_ptr<Private> d; |
108 | }; |
109 | |
110 | |
111 | === modified file 'include/com/ubuntu/property.h' |
112 | --- include/com/ubuntu/property.h 2013-11-13 09:19:50 +0000 |
113 | +++ include/com/ubuntu/property.h 2013-11-27 08:46:56 +0000 |
114 | @@ -33,13 +33,6 @@ |
115 | template<typename T> |
116 | class Property |
117 | { |
118 | - private: |
119 | - inline static T& mutable_default_value() |
120 | - { |
121 | - static T default_value = T{}; |
122 | - return default_value; |
123 | - } |
124 | - |
125 | public: |
126 | /** |
127 | * @brief ValueType refers to the type of the contained value. |
128 | @@ -47,28 +40,10 @@ |
129 | typedef T ValueType; |
130 | |
131 | /** |
132 | - * @brief Queries the default value set up for a specific type. |
133 | - * @return A non-mutable reference to the default value. |
134 | - */ |
135 | - inline static const T& default_value() |
136 | - { |
137 | - return mutable_default_value(); |
138 | - } |
139 | - |
140 | - /** |
141 | - * @brief Adjusts the default value set up for a specific type. |
142 | - * @post default_value() == new_default_value; |
143 | - */ |
144 | - inline static void set_default_value(const T& new_default_value) |
145 | - { |
146 | - mutable_default_value() = new_default_value; |
147 | - } |
148 | - |
149 | - /** |
150 | * @brief Property creates a new instance of property and initializes the contained value. |
151 | * @param t The initial value, defaults to Property<T>::default_value(). |
152 | */ |
153 | - inline explicit Property(const T& t = Property<T>::default_value()) : value{t} |
154 | + inline explicit Property(const T& t = T{}) : value{t} |
155 | { |
156 | } |
157 | |
158 | |
159 | === modified file 'include/com/ubuntu/signal.h' |
160 | --- include/com/ubuntu/signal.h 2013-11-13 09:19:50 +0000 |
161 | +++ include/com/ubuntu/signal.h 2013-11-27 08:46:56 +0000 |
162 | @@ -53,6 +53,7 @@ |
163 | |
164 | Slot slot; |
165 | Connection::Dispatcher dispatcher; |
166 | + Connection connection; |
167 | }; |
168 | |
169 | public: |
170 | @@ -63,7 +64,12 @@ |
171 | { |
172 | } |
173 | |
174 | - inline ~Signal() = default; |
175 | + inline ~Signal() |
176 | + { |
177 | + std::lock_guard<std::mutex> lg(d->guard); |
178 | + for (auto slot : d->slots) |
179 | + slot.connection.reset(); |
180 | + } |
181 | |
182 | // Copy construction, assignment and equality comparison are disabled. |
183 | Signal(const Signal&) = delete; |
184 | @@ -81,27 +87,38 @@ |
185 | */ |
186 | inline Connection connect(const Slot& slot) const |
187 | { |
188 | + // Helpers to initialize an invalid connection. |
189 | + static const Connection::Disconnector empty_disconnector{}; |
190 | + static const Connection::DispatcherInstaller empty_dispatcher_installer{}; |
191 | + |
192 | // The default dispatcher immediately executes the function object |
193 | // provided as argument on whatever thread is currently running. |
194 | static const Connection::Dispatcher default_dispatcher |
195 | = [](const std::function<void()>& handler) { handler(); }; |
196 | |
197 | + Connection conn{empty_disconnector, empty_dispatcher_installer}; |
198 | + |
199 | std::lock_guard<std::mutex> lg(d->guard); |
200 | |
201 | auto result = d->slots.insert( |
202 | d->slots.end(), |
203 | - SlotWrapper{slot, default_dispatcher}); |
204 | - |
205 | - return Connection( |
206 | - std::bind( |
207 | - &Private::disconnect_slot_for_iterator, |
208 | - d, |
209 | - result), |
210 | - std::bind( |
211 | - &Private::install_dispatcher_for_iterator, |
212 | - d, |
213 | - std::placeholders::_1, |
214 | - result)); |
215 | + SlotWrapper{slot, default_dispatcher, conn}); |
216 | + |
217 | + // We implicitly share our internal state with the connection here |
218 | + // by passing in our private bits contained in 'd' to the std::bind call. |
219 | + // This admittedly uncommon approach allows us to cleanly manage connection |
220 | + // and signal lifetimes without the need to mark everything as mutable. |
221 | + conn.d->disconnector = std::bind( |
222 | + &Private::disconnect_slot_for_iterator, |
223 | + d, |
224 | + result); |
225 | + conn.d->dispatcher_installer = std::bind( |
226 | + &Private::install_dispatcher_for_iterator, |
227 | + d, |
228 | + std::placeholders::_1, |
229 | + result); |
230 | + |
231 | + return conn; |
232 | } |
233 | |
234 | /** |
235 | @@ -148,7 +165,8 @@ |
236 | }; |
237 | |
238 | /** |
239 | - * @brief A signal class that observers can subscribe to, template specialization for signals without arguments. |
240 | + * @brief A signal class that observers can subscribe to, |
241 | + * template specialization for signals without arguments. |
242 | */ |
243 | template<> |
244 | class Signal<void> |
245 | @@ -169,6 +187,7 @@ |
246 | |
247 | Slot slot; |
248 | Connection::Dispatcher dispatcher; |
249 | + Connection connection; |
250 | }; |
251 | |
252 | public: |
253 | @@ -179,7 +198,12 @@ |
254 | { |
255 | } |
256 | |
257 | - inline ~Signal() = default; |
258 | + inline ~Signal() |
259 | + { |
260 | + std::lock_guard<std::mutex> lg(d->guard); |
261 | + for (auto slot : d->slots) |
262 | + slot.connection.reset(); |
263 | + } |
264 | |
265 | // Copy construction, assignment and equality comparison are disabled. |
266 | Signal(const Signal&) = delete; |
267 | @@ -197,25 +221,38 @@ |
268 | */ |
269 | inline Connection connect(const Slot& slot) const |
270 | { |
271 | + // Helpers to initialize an invalid connection. |
272 | + static const Connection::Disconnector empty_disconnector{}; |
273 | + static const Connection::DispatcherInstaller empty_dispatcher_installer{}; |
274 | + |
275 | // The default dispatcher immediately executes the function object |
276 | // provided as argument on whatever thread is currently running. |
277 | static const Connection::Dispatcher default_dispatcher |
278 | = [](const std::function<void()>& handler) { handler(); }; |
279 | + |
280 | + Connection conn{empty_disconnector, empty_dispatcher_installer}; |
281 | + |
282 | std::lock_guard<std::mutex> lg(d->guard); |
283 | + |
284 | auto result = d->slots.insert( |
285 | d->slots.end(), |
286 | - SlotWrapper{slot, default_dispatcher}); |
287 | - |
288 | - return Connection( |
289 | - std::bind( |
290 | - &Private::disconnect_slot_for_iterator, |
291 | - d, |
292 | - result), |
293 | - std::bind( |
294 | - &Private::install_dispatcher_for_iterator, |
295 | - d, |
296 | - std::placeholders::_1, |
297 | - result)); |
298 | + SlotWrapper{slot, default_dispatcher, conn}); |
299 | + |
300 | + // We implicitly share our internal state with the connection here |
301 | + // by passing in our private bits contained in 'd' to the std::bind call. |
302 | + // This admittedly uncommon approach allows us to cleanly manage connection |
303 | + // and signal lifetimes without the need to mark everything as mutable. |
304 | + conn.d->disconnector = std::bind( |
305 | + &Private::disconnect_slot_for_iterator, |
306 | + d, |
307 | + result); |
308 | + conn.d->dispatcher_installer = std::bind( |
309 | + &Private::install_dispatcher_for_iterator, |
310 | + d, |
311 | + std::placeholders::_1, |
312 | + result); |
313 | + |
314 | + return conn; |
315 | } |
316 | |
317 | /** |
318 | |
319 | === modified file 'tests/properties_test.cpp' |
320 | --- tests/properties_test.cpp 2013-11-13 10:41:06 +0000 |
321 | +++ tests/properties_test.cpp 2013-11-27 08:46:56 +0000 |
322 | @@ -26,16 +26,13 @@ |
323 | EXPECT_EQ(int{}, p1.get()); |
324 | |
325 | static const int new_default_value = 42; |
326 | - com::ubuntu::Property<int>::set_default_value(new_default_value); |
327 | - com::ubuntu::Property<int> p2; |
328 | + com::ubuntu::Property<int> p2{new_default_value}; |
329 | |
330 | EXPECT_EQ(new_default_value, p2.get()); |
331 | } |
332 | |
333 | TEST(Property, copy_construction_yields_correct_value) |
334 | { |
335 | - com::ubuntu::Property<int>::set_default_value(0); |
336 | - |
337 | static const int default_value = 42; |
338 | com::ubuntu::Property<int> p1{default_value}; |
339 | com::ubuntu::Property<int> p2{p1}; |
340 | @@ -45,8 +42,6 @@ |
341 | |
342 | TEST(Property, assignment_operator_for_properties_works) |
343 | { |
344 | - com::ubuntu::Property<int>::set_default_value(0); |
345 | - |
346 | static const int default_value = 42; |
347 | com::ubuntu::Property<int> p1{default_value}; |
348 | com::ubuntu::Property<int> p2; |
349 | @@ -57,8 +52,6 @@ |
350 | |
351 | TEST(Property, assignment_operator_for_raw_values_works) |
352 | { |
353 | - com::ubuntu::Property<int>::set_default_value(0); |
354 | - |
355 | static const int default_value = 42; |
356 | com::ubuntu::Property<int> p1; |
357 | p1 = default_value; |
358 | @@ -68,8 +61,6 @@ |
359 | |
360 | TEST(Property, equality_operator_for_properties_works) |
361 | { |
362 | - com::ubuntu::Property<int>::set_default_value(0); |
363 | - |
364 | static const int default_value = 42; |
365 | com::ubuntu::Property<int> p1{default_value}; |
366 | com::ubuntu::Property<int> p2; |
367 | @@ -80,8 +71,6 @@ |
368 | |
369 | TEST(Property, equality_operator_for_raw_values_works) |
370 | { |
371 | - com::ubuntu::Property<int>::set_default_value(0); |
372 | - |
373 | static const int default_value = 42; |
374 | com::ubuntu::Property<int> p1{default_value}; |
375 | |
376 | @@ -110,8 +99,6 @@ |
377 | |
378 | TEST(Property, signal_changed_is_emitted_with_correct_value_for_set) |
379 | { |
380 | - com::ubuntu::Property<int>::set_default_value(0); |
381 | - |
382 | static const int default_value = 42; |
383 | com::ubuntu::Property<int> p1; |
384 | Expectation<int> expectation{default_value}; |
385 | @@ -125,8 +112,6 @@ |
386 | |
387 | TEST(Property, signal_changed_is_emitted_with_correct_value_for_assignment) |
388 | { |
389 | - com::ubuntu::Property<int>::set_default_value(0); |
390 | - |
391 | static const int default_value = 42; |
392 | com::ubuntu::Property<int> p1; |
393 | |
394 | @@ -141,8 +126,6 @@ |
395 | |
396 | TEST(Property, signal_changed_is_emitted_with_correct_value_for_update) |
397 | { |
398 | - com::ubuntu::Property<int>::set_default_value(0); |
399 | - |
400 | static const int default_value = 42; |
401 | com::ubuntu::Property<int> p1; |
402 | |
403 | |
404 | === modified file 'tests/signals_test.cpp' |
405 | --- tests/signals_test.cpp 2013-11-13 09:19:50 +0000 |
406 | +++ tests/signals_test.cpp 2013-11-27 08:46:56 +0000 |
407 | @@ -93,6 +93,20 @@ |
408 | EXPECT_FALSE(expectation.satisfied()); |
409 | } |
410 | |
411 | +TEST(Signal, a_signal_going_out_of_scope_disconnects_from_slots) |
412 | +{ |
413 | + auto signal = std::make_shared<com::ubuntu::Signal<int>>(); |
414 | + |
415 | + auto connection = signal->connect([](int value) { std::cout << value << std::endl; }); |
416 | + |
417 | + signal.reset(); |
418 | + |
419 | + com::ubuntu::Connection::Dispatcher dispatcher{}; |
420 | + |
421 | + EXPECT_NO_THROW(connection.disconnect()); |
422 | + EXPECT_NO_THROW(connection.dispatch_via(dispatcher)); |
423 | +} |
424 | + |
425 | #include <queue> |
426 | |
427 | namespace |
428 | @@ -116,8 +130,11 @@ |
429 | std::chrono::milliseconds{500}, |
430 | [this]() { return handlers.size() > 0; }); |
431 | |
432 | - handlers.front()(); |
433 | - handlers.pop(); |
434 | + while (handlers.size() > 0) |
435 | + { |
436 | + handlers.front()(); |
437 | + handlers.pop(); |
438 | + } |
439 | } |
440 | } |
441 | |
442 | @@ -150,12 +167,10 @@ |
443 | // thread the handler is being called upon equals the thread that the |
444 | // event loop is running upon. |
445 | auto connection = s.connect( |
446 | - [&dispatcher, dispatcher_thread_id](int value, double d) |
447 | + [&dispatcher, dispatcher_thread_id](int value, double) |
448 | { |
449 | EXPECT_EQ(dispatcher_thread_id, |
450 | - std::this_thread::get_id()); |
451 | - |
452 | - std::cout << d << std::endl; |
453 | + std::this_thread::get_id()); |
454 | |
455 | if (value == expected_invocation_count) |
456 | dispatcher.stop(); |
Looks good. There are a few cases of for(auto foo : ...) which could be for(auto &foo : ...) but the performance impact is probably unnoticeable.