C++: C++-style listener list

Sometimes, it is useful to create a class to handle listeners that will be notified when something occurs in a given context. This is a common pattern (the Observer pattern) used in Java Swing, where an event triggers the invocation of one or more functions waiting for that event to occur.

In the example below, written in Java, the class instances perform some task, and the programmers want to be notified when the task has been completed:

public class Task
{
  public void doSomething() { }
 
  public void addTaskListener(TaskListener t);
}
 
public interface TaskListener
{
  void taskFinished(TaskEvent e);
}
 
public static void main(String... args)
{
  Task t = new Task();
  final String name = "TASK 123";

  t.addTaskListener(new TaskListener()
  {
    public void taskFinished(TaskEvent e)
    {
      System.out.println("Task finished: " + name);
    }
  });

  t.addTaskListener(new TaskListener()
  {
    public void taskFinished(TaskEvent e)
    {
      System.out.println("This is a second listener");
    }
  });
  t.doSomething();
}

How can this be implemented as idiomatically as possible in C++?

The first and most basic approach would be to use function pointers (in fact, an anonymous class in Java, as in the example, has access to the attributes of the class where it was defined as well as to all local variables marked as final; however, this cannot be done in the same way from an external function).

So, what would the C++ way of doing this look like?

The caller can be implemented as follows using C++ lambdas:

int main()
{
  Task t;
  std::string name = "TASK 123";
  t.addTaskListener([&name]
  {
    std::cout << "Task finished: " << name << std::endl;
  });

  t.addTaskListener([]
  {
    std::cout << "This is a second listener" << std::endl;
  });
  t.doSomething();
}

This is concise, elegant, and powerful: the name local variable can be captured by the lambda function as a closure.

The Task implementation should have a vector of listeners and should be able to access them when the task is successfully executed:

class Task
{
  private:
   std:: vector<TaskListener> listeners;
 
  public:
    void addTaskListener(TaskListener lis)
    {
      listeners.push_back(lis);
    }
 
    void doSomething()
    { 
      ...
      invokeListeners();
    }
 
  private:
    void invokeListeners()
    {
      for (TaskListener lis : listeners)
        lis();
    }
};

The problem is: How should TaskListener be declared?

Could it be a template parameter of a class template?

Answer: No.

Why?

Because each lambda function (as shown in the second example) is, under the hood, a class with a functor; so, there is no way to declare it as one single class and use it for two different classes (two lambda functions with different closures are implemented by the compiler as two unrelated classes).

As a second idea, the addTaskListener method could be implemented this way:

template <typename TaskListener>
void addTaskListener<Tasklistener t>
{
  listeners.add(t);
}

However, in that case, another new problem arises: how could the listeners vector be declared in a way that allows programmers to store one element of a given type and another of a different type?

The correct solution is to use the std::function abstraction.

std::function is a template class that can wrap a function, a functor, or a method, making it very suitable for this problem.

Thus, in the example, TaskListener could be only an alias to a std::function:

#include <functional>
 
using TaskListener = std::function<void ()>;

The parameterized type void () specifies that the function does not receive any arguments and returns void.

More on std::function here.

2 thoughts on “C++: C++-style listener list

  1. There’s a typo on line 4 of the last example, whereby TypeListener should be TaskListener. Thanks fot the post, I have learnt a lot!

Leave a reply to oopscene Cancel reply