Implementing a thread event loop using boost::bind and boost::function

Recently I was thinking about how to build a thread that has an event loop internally. If another thread wants to send us a message and have some function executed inside the event loop, how would I solve that?

Poor man’s solution

The poor man’s solution would be to have the public functions (called from another thread context) construct some kind of message struct/class that contains a ‘type’ field indicating which message should be called and some storage for the parameters. This object will then be pushed into the event queue for the event loop to read. The event loop can switch on the ‘type’ field and execute the proper method in its own context.

The problem is that this approach requires a lot of administrative overhead, if we ever change or add methods we need to update the ‘type’ list and update the switch() statement in the event loop. Cumbersome and error-prone!

Nicer solution using boost::bind and boost::function

So that brings me to my solution: what if there was some way to have the public function construct a function-pointer like object that also stores it’s arguments in the same object? We could put these objects in a thread safe queue and have the event loop just blocking read on that queue. When the event loop notices a new event, it takes the function pointer and executes it in its own thread context.

Enter boost::function! It’s a very clever function pointer that can store arbitrary arguments as well. To create such an object, we use boost::bind to store the parameters and point it to the right object to execute the member function on. Sample code (scroll to the right if text disappears):

#include <iostream>
#include <queue>
#include <string>
#include <boost/function.hpp>
#include <boost/bind.hpp>

/*
 * This is the function pointer we will store and call.
 * It just means it's a function with void return type and no params.
 * Slightly confusing perhaps, but see below that we can actually still
 * bind method parameters and store their values.
 * This definition is only about how we should call the pointer from
 * the event loop, bound parameters do the rest.
 */
typedef boost::function<void(void)> FunctionPointer;

/*
 * Our stupid class
 */
class EventProcessor
{
public:
   /*
    * Pretend these methods will be called from another thread
    * NOTE: no locking is implemented here for simplicity
    */

   void dostuff1(int x)
   {
      /* All C++ member functions secretly have a 'this' pointer
       * as first param, we need to bind that to the object where
       * we want to execute our member function. That's what the first
       * two bind params are about. In this case we just specify the
       * 'this' value of the current object but it might well
       * be another object
       *
       * First argument: bind to this member function
       * Second argument: the member function will be called on this object
       * Third argument: store the value of x
       *
       * NOTE: We're actually storing a pointer to a private member
       * function here, probably because of magic trickery done by
       * boost::bind/boost::function we get around that :)
       */
      FunctionPointer f = boost::bind(
            &EventProcessor::dostuff1_impl, this, x);
      events.push(f);
   }

   void dostuff2(int y)
   {
      FunctionPointer f = boost::bind(
            &EventProcessor::dostuff2_impl, this, y);
      events.push(f);
   }

   void dostuff3(std::string &text)
   {
      /* Hey look at this trick, we have a reference to string
       * but still a complete copy is stored
       */
      FunctionPointer f = boost::bind(
            &EventProcessor::dostuff3_impl, this, text);
      events.push(f);
   }

   /*
    * This is normally running inside a thread internal to our EventProcessor
    * But we keep it simple so we call it from our main function as well
    */
   void eventloop()
   {
      while (!events.empty())
      {
         FunctionPointer f = events.front();
         f();
         events.pop();
      }
   }
private:
   /* Actual implementation functions called from event loop,
    * all methods run on the internal thread */
   void dostuff1_impl(int x)
   {
      std::cout << "dostuff1 " << x << std::endl;
   }
   void dostuff2_impl(int x)
   {
      std::cout << "dostuff2 " << x << std::endl;
   }
   void dostuff3_impl(std::string &text)
   {
      std::cout << "dostuff3 " << text << std::endl;
   }
   std::queue<FunctionPointer> events;
};

int main()
{
   EventProcessor p1;

   // In this part of the code no methods are executed yet
   p1.dostuff1(100);
   p1.dostuff2(2001);

   // a copy is performed as you will see later on
   std::string payload = "lama";
   p1.dostuff3(payload);

   /* if it was still a reference, when we execute the loop
    * we would be seeing "test123" as dostuff3 text */
   payload = "test123";

   // Now iterate over the queue and execute each pointer
   p1.eventloop();
}

Please note that there are no real threads in this example, I left them out for clarity. Output:

dostuff1 100
dostuff2 2001
dostuff3 lama

For more info on boost::bind see my earlier post at Graphical explanation of boost::bind