Boost logo

Boost :

From: Douglas Gregor (gregod_at_[hidden])
Date: 2001-07-02 22:54:41


Hello all,
        I've written a prototype signals & slots library and would appreciate some
feedback from those interested. The library is available at:
http://groups.yahoo.com/group/boost/files/Callback/sigslot.tgz

        I'll try to capture the essence of the library here:

Signals are declared with the return type and argument types of the slots
that they contain. They may container zero or more slots, and execute all of
the contained slots when the function call operator is invoked.

Slots can be any 'compatible' function or function object (same definition of
'compatible' as used in Boost.Function). An example:

---
  boost::signal2<float, float, float, max_or_default<float> > smax;
  smax.connect(std::multiplies<float>());
  boost::connection c1 = smax.connect(&pow);
  smax.connect(std::plus<float>());
 
  std::cout << smax(1.0,2.0) << std::endl; // outputs 3
  std::cout << smax(3.0,2.0) << std::endl; // outputs 9
  c1.disconnect();
  std::cout << smax(3.0,2.0) << std::endl; // outputs 6
---
The first three template arguments to boost::signal2 are the slot return 
value and the two slot arguments; the final argument is the type of a 
"combiner" that takes the results of all slots and returns the maximum value 
(or returns a default-constructed value if there are no slots to call). 
Combiner function objects have a simple syntax: they take two input iterators 
[first, last) and choose their value from the values returned by the 
iterators. The value the combiner returns is the value returned by the slot, 
i.e.,
---
template<typename T>
struct max_or_default {
  typedef T result_type;
  template<typename InputIterator>
  typename InputIterator::value_type
  operator()(InputIterator first, InputIterator last) const
  {
    if (first == last)
      return T();
 
    T max = *first++;
    for (; first != last; ++first)
      max = (*first > max)? *first : max;
 
    return max;
  }
};
---
The "connect" member function of a signal connects that signal to the slot 
argument. It returns a connection object that may be queried (does the 
connection between signal and slot still exist?) and disconnected. 
Signals & slots are safe when used with the right objects. A "bindable" type 
is available that should be a base class of any object that will be bound to 
create a slot. For instance, if I have a type X and I use it to connect a 
slot:
struct X : public boost::bindable {
  float foo(float x,  float y) const { return 2*x+y; }
};
X* x = new X();
smax.connect(boost::bind(&X::foo, x, _1, _2));
When "x" is deleted, the connection between the smax signal and the slot 
disappears, so segfaults can be avoided. This may occur even when smax has 
invoked the slot that is being removed.
	To build the examples & testcases, you will need Boost.Function from CVS and 
the PREPROCESSOR library currently under review. A modified version of Peter 
Dimov's bind.hpp is included that implements the interface needed by signals 
& slots. The build portion is very Unix-centric (sorry) and I've worked 
primary with GCC (2.95.3/3.0) and Comeau (4.2.45.2). Experiences from other 
platforms absolutely welcome.
Things I'd most like feedback on:
	- User interface. Ways to make it cleaner? Easier to use? Especially:
		- Combiner interface
		- Handling connections
		- Names for the 'bindable' base class
	- Binder interface. This should be agreed upon by any who will be involved 
in maintaining it. For signals & slots to work with a bind library (lambda, 
bind, etc, etc) some free functions must be defined which notify a visitor of 
each object referenced within the binding.  Signals & slots will not have its 
own bind library, ever.
	- Additional features? 
	Thanks,
	Doug

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk