boost.png (6897 bytes) Home Libraries People FAQ More

PrevUpHomeNext

Example: the producer/consumer pattern

Generators can be used to straightforwardly model the producer/consumer pattern. In this scenario one function generates values and another consumes them. The solution presented here is consumer driven, that is, the consumer dictates the speed at witch the producer generates values. In this example the producer generates all permutations of a given string, while the consumer simply print the output:

typedef coro::generator<int> generator_type;

const std::string& producer(generator_type::self& self, std::string base) {
  std::sort(base.begin(), base.end());
  do {
    self.yield(base);
  } while (std::next_permutation(base.begin(), base.end()));
  self.exit();
}

template<typename Producer> 
void consumer(Producer producer) {
  do {
    std::cout <<*producer << "\n";
  } while(++producer);
}

...
consumer
 (generator_type
  (boost::bind
   (producer, _1, std::string("hello"))));
...
generator correctly handle const and non-const references. You can even return a reference to a local object, but you must make sure that the object doesn't go out of scope while it is in use. This is why this example uses operator* and operator++ instead of the simpler operator(). In fact this last member function correspond to *(*this)++. Consider what would happen at the last iteration: it would first copy the iterator (and thus store a reference to the last generated value), then increment it, restarting the generator body that would call exit(), destroying the local string and invalidating the reference; finally it would return the dangling reference. Splitting the calls to the two member functions gives us a window where the reference is live.

Filters

This pattern is very useful and can be extended to insert another filter function between the producer and the consumer. This filter is both a producer and a consumer: it return the result of a call to the parameter producer with the string " world" appended:

struct filter {
  typedef const std::string& result_type;

  template<typename Producer>
  const std::string& operator()
    (generator_type::self& self, Producer producer) {
    do {
      self.yield(*producer + " world");
    } while(++producer);
    self.exit();
  }
};

consumer
  (generator_type
   (boost::bind
    (filter(),
     _1,
     generator_type
     (boost::bind
producer, _1, std::string("hello"))))));
We have made filter a function object instead of a plain function because it is a template. If it were a template function, the compiler wouldn't know which function pointer pass to bind. This is just one of the multiple solutions to this recurring problem.

You can obviously have as many filters functions as you want.

Copyright 2006 Giovanni P. Deretta

PrevUpHomeNext