C++11 generic observer pattern

c11 generic observer pattern - theimpossiblecode.com

The observer pattern is a very common design pattern to use for event driven programming. Actually this pattern existed for many years in a form of simple callbacks and here we’ll try to reduce it back to such a simple idea. In this post we’re also going to encapsulate it into reusable code by making it a C++11 generic observer pattern. This will make event driven programming as easy as pie wherever you’ll need it.

Using C++11

The solution described here will be based on template parameter pack and the std::function. C++11 added both of these capabilities.

Why write a C++11 generic observer pattern?

Why at all?

Design patterns are proven solutions to daily coding/design tasks. Hence, a generic pattern, based on templates, will make it easier for you to take this proven solution and easily apply it to your types without rewriting the code. Furthermore template based solutions are compile time type safe and help avoid runtime errors you might get from using the wrong types.

Why write your own?

You could use an external solution, like boost.signals2 (a little too bloated IMHO), but sometimes this solution is either not fitting or you want to avoid an external dependency for something relatively small. Many times writing something small which fits exactly what you need can also be more efficient at runtime since it has your minimum tailored requirements.

Example usage

  • First of all see below how simple it is to define a dispatcher for any number of arguments of any type.
  • In additional, although the usage of the dispatcher will work for anything callable, we’ll see here the 2 “extreme cases” – from the easiest to code, with lambda expressions (once you get used to lambda expressions they save you a ton of time), to the most tedious way of using a private method in a class as a callback that should be invoked on a specific instance of this class.

Requirements

  • A dispatcher will invoke user supplied callbacks.
  • We want to declare a dispatcher on the spot with any callback signature.
  • The compiler will check that the callback signature matches the dispatcher using it.
  • The dispatcher must be able to add and remove callbacks fast.
  • Any callable code can be used as a callback – lambda expressions / object methods / functions / functors.

Implementation explained

  • The implementation is based on the C++11 template parameter pack and std::function.
  • C++11 template parameter pack allows us in our case to define a template which accepts any number of arguments, and  use them at the prototype of the Dispatcher::broadcast() method, with:
template <typename... Args>
class Dispatcher
{
public:
...
 void broadcast(Args... args);
...
};

(see more on C++11 template parameter pack here).

  • std::function accepts any callable object and it is what we’ll keep in an internal list of callbacks (see more on std::function here).
  • We’ll use std::list to keep the callbacks since we can use its iterators to efficiently add and remove items to/from anywhere the list.
    • Adding a callback is a simple push_back() to a std::list.
    • The CBID implementation is using this to help delCB() locate the callback object fast.
  • This is all the magic, and the implementation is short enough:

Building and running the example

$ g++ -std=c++11 dispatcher_example.cpp
$ ./a.out

Summary

What we’ve got?

This is a fully functional observer pattern for event driven programming. It fulfills the requirements we need, it is very easy to use and by that it promotes good code usage in our system. Any further improvements we may want to add can fit under the hood of this simple API.

What to improve?

This depends on your requirements and has some trade-offs, but some of the more obvious things you might want to do are:

  • The CBID can hold a reference to the Dispatcher it belongs to and have a method to remove() from it.
    • trade-off: holding an additional pointer per CBID might have an impact on RAM, depending on your use case.
  • delCB() can be re-entrant in such a way that invoking a callback which removes a CBID won’t harm the broadcast() loop.
    • trade-off: reduces  a little the observer pattern simplicity (but it’s perfectly fine IMHO).
  • Whatever you need…

That’s all for today,
Have a lovely weekend everyone!

Sagi Zeevi

20 years writing code and still loving it!

You may also like...

1 Response

  1. October 28, 2017

    […] Maybe you also want to read about the C++11 generic observer pattern. […]

Scroll Up