The Boost C++ Libraries has been updated. The second edition was published in September 2014 and introduces 72 Boost libraries with more than 430 examples. It is available at Amazon, Barnes and Noble, for Kindle, as an Epub and as a PDF file. The second edition is based on C++11 and the Boost libraries 1.55.0 and 1.56.0 with the latter version having been released in August 2014.

Find the second edition online at http://theboostcpplibraries.com/

The Boost C++ Libraries


Chapter 4: Event Handling


Table of Contents

This book is licensed under a Creative Commons License.


4.1 General

Many developers think about graphical user interfaces when hearing the term 'event handling': At the click of a button, the associated function is executed. The click itself is the event while the function is the corresponding event handler.

The usage of this pattern is certainly not limited to graphical user interfaces though. In general, arbitrary objects can call dedicated functions based on specific events. The Boost.Signals library introduced in this chapter provides an easy way to apply this pattern in C++.

Strictly speaking, the Boost.Function library could also be used for event handling. One crucial difference between Boost.Function and Boost.Signals, however, is the ability of Boost.Signals to associate more than one event handler with a single event. Therefore, Boost.Signals supports event-driven development much better and should be the first choice whenever events must be handled.


4.2 Signals

While the name of the library seems to be a bit misleading at first, it actually is not. Boost.Signals implements a pattern named signal to slot which is based on the concept that associated slots are executed once the corresponding signal is issued. In principle, one can substitute the words 'signal' and 'slot' with 'event' and 'event handler', respectively. However, since signals can be issued at any given time, the concept renounces events.

Consequently, Boost.Signals does not offer classes that resemble events. Instead, it provides a class named boost::signal defined in boost/signal.hpp. This header file is actually the only one required knowing since it will include dependent header files automatically.

Boost.Signals defines additional classes residing in the boost::signals namespace. Since boost::signal is the most commonly used class, it actually resides in the namespace boost instead.

#include <boost/signal.hpp> 
#include <iostream> 

void func() 
{ 
  std::cout << "Hello, world!" << std::endl; 
} 

int main() 
{ 
  boost::signal<void ()> s; 
  s.connect(func); 
  s(); 
} 

boost::signal is actually implemented as a template function expecting the signature of a function, used as the event handler, as its template parameter. In the example, only functions with a signature of void () can be associated with the signal s successfully.

The function func() is associated with the signal s using the connect() method. Since func() conforms to the required void () signature, the association is successfully established. func() is therefore called whenever the signal s is triggered.

The signal is triggered by calling s just like a regular function. The signature of this function corresponds to the one passed as the template parameter: The brackets are empty since void () does not expect any parameters.

Calling s results in a trigger which in turn executes the func() function - previously associated using connect() - accordingly.

The same example can also be realized using Boost.Function instead.

#include <boost/function.hpp> 
#include <iostream> 

void func() 
{ 
  std::cout << "Hello, world!" << std::endl; 
} 

int main() 
{ 
  boost::function<void ()> f; 
  f = func; 
  f(); 
} 

Similar to the previous example, func() is associated with f. Once called, func() is executed accordingly. While Boost.Function is limited to these scenarios, Boost.Signals offers far more variety such as associating multiple functions with a particular signal as shown in the following example.

#include <boost/signal.hpp> 
#include <iostream> 

void func1() 
{ 
  std::cout << "Hello" << std::flush; 
} 

void func2() 
{ 
  std::cout << ", world!" << std::endl; 
} 

int main() 
{ 
  boost::signal<void ()> s; 
  s.connect(func1); 
  s.connect(func2); 
  s(); 
} 

boost::signal allows assigning multiple functions to a particular signal by calling the connect() method repeatedly. Whenever the signal is triggered, the functions are executed in the order they have been previously associated using connect().

Alternatively, the order can be explicitly specified using an overloaded version of the connect() method that expects a value of type int as an additional parameter.

#include <boost/signal.hpp> 
#include <iostream> 

void func1() 
{ 
  std::cout << "Hello" << std::flush; 
} 

void func2() 
{ 
  std::cout << ", world!" << std::endl; 
} 

int main() 
{ 
  boost::signal<void ()> s; 
  s.connect(1, func2); 
  s.connect(0, func1); 
  s(); 
} 

As with the previous example, func1() is executed before func2().

To release an associated function with a given signal, the disconnect() method is used.

#include <boost/signal.hpp> 
#include <iostream> 

void func1() 
{ 
  std::cout << "Hello" << std::endl; 
} 

void func2() 
{ 
  std::cout << ", world!" << std::endl; 
} 

int main() 
{ 
  boost::signal<void ()> s; 
  s.connect(func1); 
  s.connect(func2); 
  s.disconnect(func2); 
  s(); 
} 

This example only prints Hello since the association with func2() has been released prior to triggering the signal.

Besides connect() and disconnect(), boost::signal offers only a few additional methods.

#include <boost/signal.hpp> 
#include <iostream> 

void func1() 
{ 
  std::cout << "Hello" << std::flush; 
} 

void func2() 
{ 
  std::cout << ", world!" << std::endl; 
} 

int main() 
{ 
  boost::signal<void ()> s; 
  s.connect(func1); 
  s.connect(func2); 
  std::cout << s.num_slots() << std::endl; 
  if (!s.empty()) 
    s(); 
  s.disconnect_all_slots(); 
} 

num_slots() returns the number of associated functions. If no function is associated, num_slots() returns 0 accordingly. In this particular case, the empty() method can be used instead. The disconnect_all_slots() method actually does exactly what its name expresses: It releases all existing associations.

After seeing how functions are associated with signals as well as understanding what happens once a signal is triggered, one question actually remains: What happens to the return values of these functions? The following example answers this question.

#include <boost/signal.hpp> 
#include <iostream> 

int func1() 
{ 
  return 1; 
} 

int func2() 
{ 
  return 2; 
} 

int main() 
{ 
  boost::signal<int ()> s; 
  s.connect(func1); 
  s.connect(func2); 
  std::cout << s() << std::endl; 
} 

Both func1() and func2() now have a return value of type int. s processes both return values and writes them to the standard output stream somehow. However, what happens exactly?

The above example will actually write 2 to the standard output stream. Both return values were correctly accepted by s but were ignored except for the last one. By default, only the last return value of all associated functions is actually returned.

It is possible to customize a signal so that the individual return values are processed accordingly. For that purpose, a so-called combiner must be passed to boost::signal as the second parameter.

#include <boost/signal.hpp> 
#include <iostream> 
#include <algorithm> 

int func1() 
{ 
  return 1; 
} 

int func2() 
{ 
  return 2; 
} 

template <typename T> 
struct min_element 
{ 
  typedef T result_type; 

  template <typename InputIterator> 
  T operator()(InputIterator first, InputIterator last) const 
  { 
    return *std::min_element(first, last); 
  } 
}; 

int main() 
{ 
  boost::signal<int (), min_element<int> > s; 
  s.connect(func1); 
  s.connect(func2); 
  std::cout << s() << std::endl; 
} 

A combiner is a class overloading the operator()() operator. This operator is automatically called with two iterators pointing to all return values for a particular signal. The example uses the standard C++ algorithm std::min_element() to determine and return the smallest value.

Unfortunately, it is not possible to pass an algorithm such as std::min_element() directly to boost::signal as a template parameter. boost::signal expects that the combiner defines a type called result_type that denotes the type of the value returned by the operator()() operator. Since this type is omitted by the standard C++ algorithms, an error is issued during compilation accordingly.

Instead analyzing return values, a combiner can also save them.

#include <boost/signal.hpp> 
#include <iostream> 
#include <vector> 
#include <algorithm> 

int func1() 
{ 
  return 1; 
} 

int func2() 
{ 
  return 2; 
} 

template <typename T> 
struct min_element 
{ 
  typedef T result_type; 

  template <typename InputIterator> 
  T operator()(InputIterator first, InputIterator last) const 
  { 
    return T(first, last); 
  } 
}; 

int main() 
{ 
  boost::signal<int (), min_element<std::vector<int> > > s; 
  s.connect(func1); 
  s.connect(func2); 
  std::vector<int> v = s(); 
  std::cout << *std::min_element(v.begin(), v.end()) << std::endl; 
} 

The example saves all the return values in a vector which in turn is returned by s().


4.3 Connections

Functions can be managed with the aid of the connect() and disconnect() methods provided by boost::signal. Due to connect() returning a value of type boost::signals::connection, they can be managed differently as well.

#include <boost/signal.hpp> 
#include <iostream> 

void func() 
{ 
  std::cout << "Hello, world!" << std::endl; 
} 

int main() 
{ 
  boost::signal<void ()> s; 
  boost::signals::connection c = s.connect(func); 
  s(); 
  c.disconnect(); 
} 

The disconnect() method of boost::signal requires a function pointer being passed which can be avoided by calling the disconnect() method on the boost::signals::connection object directly.

In addition to the disconnect() method, boost::signals::connection also provides methods such as block() and unblock().

#include <boost/signal.hpp> 
#include <iostream> 

void func() 
{ 
  std::cout << "Hello, world!" << std::endl; 
} 

int main() 
{ 
  boost::signal<void ()> s; 
  boost::signals::connection c = s.connect(func); 
  c.block(); 
  s(); 
  c.unblock(); 
  s(); 
} 

The above program executes func() exactly once. Even though the signal s is triggered twice, func() is not called for the first trigger since the connection c has actually been blocked by a call to block(). Since unblock() is called before the second trigger, func() is now executed correctly.

Besides boost::signals::connection, a class named boost::signals::scoped_connection is offered that releases the connection during destruction automatically.

#include <boost/signal.hpp> 
#include <iostream> 

void func() 
{ 
  std::cout << "Hello, world!" << std::endl; 
} 

int main() 
{ 
  boost::signal<void ()> s; 
  { 
    boost::signals::scoped_connection c = s.connect(func); 
  } 
  s(); 
} 

Since the connection object c is destroyed before the signal is triggered, func() is not being called.

boost::signals::scoped_connection is actually derived from boost::signals::connection and thus offers the same methods. The only difference is the automatic release of the connection during destruction of boost::signals::scoped_connection.

While boost::signals::scoped_connection certainly makes it easier to automatically release connections, objects of this type still need to be managed. It would be nice if connections could be automatically released in other cases as well without having the need to actually manage these objects.

#include <boost/signal.hpp> 
#include <boost/bind.hpp> 
#include <iostream> 
#include <memory> 

class world 
{ 
  public: 
    void hello() const 
    { 
      std::cout << "Hello, world!" << std::endl; 
    } 
}; 

int main() 
{ 
  boost::signal<void ()> s; 
  { 
    std::auto_ptr<world> w(new world()); 
    s.connect(boost::bind(&world::hello, w.get())); 
  } 
  std::cout << s.num_slots() << std::endl; 
  s(); 
} 

The above program associates the method of an object with a signal by using Boost.Bind. The object is destroyed before the signal is triggered which creates an issue. Opposed to passing the actual object w, only a pointer has been passed to boost::bind() instead. By the time s() is actually called, the object referenced by the pointer does no longer exist.

It is possible to modify the program so that the connection is automatically released once the object w is destroyed.

#include <boost/signal.hpp> 
#include <boost/bind.hpp> 
#include <iostream> 
#include <memory> 

class world : 
  public boost::signals::trackable 
{ 
  public: 
    void hello() const 
    { 
      std::cout << "Hello, world!" << std::endl; 
    } 
}; 

int main() 
{ 
  boost::signal<void ()> s; 
  { 
    std::auto_ptr<world> w(new world()); 
    s.connect(boost::bind(&world::hello, w.get())); 
  } 
  std::cout << s.num_slots() << std::endl; 
  s(); 
} 

If executed now, num_slots() actually returns 0 to make sure that no method is tried to be called on an already destroyed object. The only change necessary was to derive the world class from boost::signals::trackable. Whenever pointers to objects instead of object copies are used to associate functions with signals, boost::signals::trackable can simplify the management of the connections considerably.


4.4 Exercises

You can buy solutions to all exercises in this book as a ZIP file.

  1. Create a program by defining a class named button which represents a clickable button within a graphical user interface. Add two methods add_handler() and remove_handler(), both expecting a function name as a parameter, to the class. If a click() method is called, the registered functions should be executed sequentially.

    Test your code by creating an instance of the button class and writing a message to the standard output stream from within the event handler. Call the click() function to simulate a mouse click on the button.