Simple observer pattern using modern C++

Valle Onsernone in Ticino

Simple observer pattern using modern C++

Originally, I learned C/C++ around 2006 when pointers were still going strong. I think, I’ve never actually pushed any C++ to production. The closest to that was the flight simulator based on Ogre and a custom simulation model for my high school thesis. It’s still available on sourceforge.net by the way. Ah, SourceForge, the place to be before GitHub took off…

In April, I’ve got the chance to brush up my C++ experience when working in a university lab. I want to share with you this neat little class I wrote during this time. It’s an implementation of the observer pattern that follows the resource acquisition is initialization (RAII) principle from C++.

The observer pattern is useful if you have a producer (e.g. camera that produces images) and multiple consumers. In my case, the consumers were the UI and a number of image processing algorithms for object tracking. I needed a way to be notified by the producer without having to worry about dangling pointers or references. Thanks to std::weak_ptr and variadic templates, the implementation is very small and needs low ceremony. Note that it’s neither foolproof (deadlocks) nor thread-safe (observer callback is called from thread that calls notify()). But thread safety can be achieved easily by writing to std::atomic or by pushing to a thread-safe queue, etc.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#ifndef UTIL_SUBJECT_H
#define UTIL_SUBJECT_H

#include <memory>
#include <forward_list>
#include <functional>

/**
 * Simple observer pattern in modern C++ without pointers.
 * Register functions with make_observer() and call notify() to call them.
 * Note: Functions are called from the thread that calls notify().
 */
template <typename... Args>
class Subject
{
public:
    typedef std::shared_ptr<std::function<void(Args...)>> Observer;

    Subject() {}

    void notify(const Args &...args)
    {
        bool cleanup = false;
        for (const auto &observer_weak : m_observers)
        {
            if (const auto &observer_function = observer_weak.lock())
            {
                (*observer_function)(args...);
            }
            else
            {
                // weak pointer expired, do a cleanup round
                cleanup = true;
            }
        }

        if (cleanup)
        {
            m_observers.remove_if([](auto observer) { return observer.expired(); });
        }
    }

    /**
     * Register a function as observer. Keep the returned shared_ptr around as long as you want
     * the function to be called.
     */
    Observer makeObserver(std::function<void(Args...)> observerFunction)
    {
        auto observer = std::make_shared<std::function<void(Args...)>>(observerFunction);
        m_observers.emplace_front(observer);
        return observer;
    }

private:
    std::forward_list<std::weak_ptr<std::function<void(Args...)>>> m_observers;
};

#endif // UTIL_SUBJECT_H

Example usage:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include "subject.h"

void fun1(int x, std::string s) {
    std::cout << "fun1: " << x << " " << s << std::endl;
}

void fun2(int x, std::string s) {
    std::cout << "fun2: " << x << " " << s << std::endl;
}

int main() {
    Subject<int, std::string> s;
    s.notify(0, "heard by none");
    {
        auto o1 = s.makeObserver(fun1);
        s.notify(1, "heard only by fun1");
        {
            auto o2 = s.makeObserver(fun2);
            s.notify(2, "heard by fun1 and fun2");
        }
        s.notify(3, "heard only by fun1");
    }
    s.notify(4, "heard by none");
}