Observer/Listener structures in C++ with boost’s smart pointers

Whenever you are developing sufficiently large complex programs in languages like C++ or Java you have to deal with memory issues. This holds true especially when your program is supposed to run 24/7 or close to that. Because these kinds of issues can be hard to get right Java has this nice little helper, the garbage collector. But as Java solves all memory problems, or maybe not? points out, you can still easily shoot yourself in foot or even blow your whole leg away.  One of the problems stated there is that memory leaks can easily occur due to incorrect listener relations. Whenever a listener is not removed properly, which is either a large object itself or has references to such objects,  it’s only a matter of time until your program dies with “OutOfMemoryError” as its last words.  One of the proposed solutions is to use Java weak pointers for listener management.  Let’s see how this translates to C++.

Observer/listener management in C++ is often done using pointers to listener objects. Pointers are pretty weak by default. They can be :

  • null
  • pointing to a valid object
  • pointing to an invalid memory address

In listener relationships especially the latter can be a problem. For example, simple listener management could look like this:

   class SimpleListenerManagement
   {
   public:
      void addListener(MyListener* listener);
      void removeListener(MyListener* listener);
      void notifyListeners();
   private:
      std::list<MyListener*> listeners_;
   };

   void SimpleListenerManagement::notifyListeners()
   {
      // call notify on all listeners
      for (std::list<MyListener*>::iterator iter = listeners_.begin();
          iter != listeners_.end();
          ++iter)
      {
         (*iter)->notify(); // may be a bad idea!
      }
   }

In notifyListeners(), the pointer is used trusting that it still points to a valid object. But if it doesn’t, for instance because the object was deleted but the client forgot to removed it from the listener management, well, too bad.

Obviously, the situation would be much better if we didn’t use raw pointers but some kind of wrapper objects instead.  A first improvement would be to use boost::shared_ptr in the listener management:

   typedef boost::shared_ptr<MyListener> MyListenerPtr;

   class SimpleListenerManagement
   {
   public:
      void addListener(MyListenerPtr listener);
      void removeListener(MyListenerPtr listener);
      void notifyListeners();
   private:
      std::list<MyListenerPtr> listeners_;
   };

Provided that the given MyListenerPtr instance was created correctly by the client we can be sure now that all listeners exist when we call notify() on them.  Seems much better now. But wait! Using boost::shared_ptr, we now hold  strong references in our listeners list and are therefore kind of in the same situation as described in the post mentioned above. If the client forgets to remove its MyListenerPtr instance it never gets deleted and may be in a invalid state next time notify() is called.

A solution that works well in most cases is to use boost::weak_ptr to hold the listeners. If you see boost::shared_ptr on a level with normal Java references, boost::weak_ptrs are roughly the same as Java’ s weak references. Our listener management class would then look like this:

   typedef boost::shared_ptr<MyListener> MyListenerPtr;
   typedef boost::weak_ptr<MyListener> MyListenerWeakPtr;

   class SimpleListenerManagement
   {
   public:
      void addListener(MyListenerPtr listener);
      void removeListener(MyListenerPtr listener);
      void notifyListeners();
   private:
      std::list<MyListenerWeakPtr> listeners_; // using weak_ptr
   };

Note that addListener and removeListener still use MyListenerPtr as parameter. This ensures that the client provides valid listener objects.  The interesting stuff happens in notifyListeners():

   void SimpleListenerManagement::notifyListeners()
   {
      std::list<MyListenerWeakPtr>::iterator iter = listeners_.begin();
      while(iter != listeners_.end())
      {
         if ((*iter).expired())
         {
            iter = listeners_.erase(iter);
         }
         else
         {
            MyListenerPtr listener = (*iter).lock(); // create a shared_ptr from the weak_ptr
            listener->notify();
            ++iter;
         }
      }
   }

Each weak_ptr can now be checked if its object still exists before using it. If the weak_ptr is expired, it can simply be removed from the listeners list. With this implementation the removeListener method becomes optional and can as well be omitted. The client only has to make sure that the shared_ptr holding the listener gets deleted somehow.

6 thoughts on “Observer/Listener structures in C++ with boost’s smart pointers”

  1. Very useful article.
    But it would be helpful to see how you implement addListener() and removeListener(). Since you use weak_ptr in STL container, it would be impossible to search for a specific listener in a list, because weak_ptr has no comparison operator. And while it is harmless to omit removeListener(), how would you check for double addition of the same listener in addListener()?

    1. @John: Thanks for your comment. Actually, weak_ptr has a comparison operator. It only disguises itself in free function:

      template<class T, class U>
      bool operator<(weak_ptr<T> const & a, weak_ptr<U> const & b);

      Equivalence is then defined as

      !(a < b) && !(b < a)

      See the weak_ptr documentation for details.

      Since this operator imposes a strict weak ordering on weak_ptr instances you can, in fact, use all sorted associative container defined in STL. As an improvement for my example you could use std::set instead of std::list. In addListener you could then say something like this:


      if (listeners_.count(listener) > 0)
      {
      // listener already exists

      }

  2. If you’re using boost already then there is a better flexible and less intrusive alternative boost::signals.

  3. What I have done is to use boost weak pointers in signals. Therefore, when the Observer goes away, the connection is automatically lost.

  4. Before calling notify(), you check that listener contains the valid pointer because the listener may have been released between the time you check its expiration and the time you lock it.

    Better yet, don’t check the expiration, only check the result of lock():

    while(iter != listeners_.end())
    {
    MyListenerPtr listener = (*iter).lock(); // create a shared_ptr from the weak_ptr
    if (!listener)
    {
    iter = listeners_.erase(iter);
    }
    else
    {
    listener->notify();
    ++iter;
    }
    }

  5. Pingback: reviews

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.