Boost logo

Boost :

From: Jesse Jones (jesjones_at_[hidden])
Date: 2001-05-07 05:08:54


> > It'd be great if we could address these two domains with a single
> > class, but it seems like we're going to have to make a trade-off
>> somewhere. Personally I think that callbacks are used a lot more
>> often than functors. And being able to compare two callbacks *is*
>> useful functionality. If this requires that clients have to define an
>> operator== for function objects that would be unpleasant, but worth
>> the ugliness from my POV.
>>
>> -- Jesse
>
>I'm not entirely convinced that comparing two boost::function objects is
>necessary for the callback domain. I don't currently deal with callbacks
>much, but in my prior life as a GUI person I don't recall ever needing to
>compare callbacks.

Well, there's very little that you really have to have. I have found,
however, that being able to compare callbacks is helpful in creating
simpler, less coupled designs.

>In the timer example you gave it is convenient to have function pointers, but
>I'm not sure that's the "right way" to do things because it places extra
>requirements on the caller that may be tough to handle: what if my timer
>could point to one of several different callback targets? Then I have to keep
>track of which one was assigned anyway - is this better than keeping track of
>a "Timer" object (or a "TimerHandle" object) that would be used to remove the
>timer?

I'm not quite sure what you're saying here.

>What if two timers end up pointing to the same type of function
>object?

I consider registering a callback twice a precondition violation.
Here's the entire public API:

class ITimer : public XUnknown {

public:
     // ----- Adding -----
     virtual void AddTimer(XCallback0<void> f, MilliSecond interval) = 0;
                  /**< Interval is the amount of time you want to wait
before calls
                  to the callback. Note that the actual interval may
occasionally be
                  larger than the specified interval (but it won't be
smaller). */

     virtual void AddTimer(XCallback0<void> f, MilliSecond interval,
MilliSecond delay) = 0;
                  /**< Allows you to specify the amount of time to
wait before the
                  timer is called the very first time. Defaults to the
interval time. */

     virtual void AddOneShotTimer(XCallback0<void> f, MilliSecond delay) = 0;
                  /**< Installs a timer that will only be called once
(you can use
                  SetInterval to switch over to periodic time). */

     // ----- Mutators -----
     virtual void SetInterval(XCallback0<void> f, MilliSecond interval) = 0;
                  /**< Updates the interval of a previously added callback. */

     virtual void SetDelay(XCallback0<void> f, MilliSecond delay) = 0;
                  /**< The timer won't fire until delay msecs elapse
at which time
                  it will fire every interval msecs (this is just like calling
                  RemoveTimer and then AddTimer with a new delay). */

     // ----- Removing -----
     virtual void RemoveTimer(XCallback0<void> f) = 0;
                  /**< Note that it's ok to call this if the callback
hasn't been added. */
};

I think this is quite nice: it's dirt simple to use, very cohesive,
and has low coupling. I think you were arguing for having a timer
object embedded in each object that wants time. This is certainly
workable, but it's a bit more cumbersome since you have to, at the
very least, add the timer to your class declaration. And your class's
cohesion is reduced because you have this icky helper object embedded
inside your class.

>I'm almost wondering if there really is a fundamental difference between the
>two domains or if the lack of tools to enable the use of the "functor" domain
>in C++ is the cause of the difference. If the binder and lambda libraries
>were generally available and understood, would we be so reliant on the
>standard callback types, to functions and member functions?

Here are the bulk of the places I use callbacks: to respond to
clicking on controls, to handle Undo/Redo (using the Command
pattern), to handle document state notifications (instead of the
Observer pattern), to respond to key events, to respond to menu
commands, to enable menu items, to get time, to allow users to
install custom error handlers , to specify the entry point and error
handlers for a thread, to support thread IOUs (aka futures), and to
call a callback for each node in an IHierarchy with an optional
predicate callback (this is normally the app, documents, windows, and
controls).

Of all of these only the last seems the sort of thing the lambda
library is good for. Timers, document notification, key handlers, and
the menu handlers all rely on comparing callbacks.

>I think that requiring operator== of every function object is too
>restrictive: standard library function objects don't support it, and likely
>neither will functors created by boost::bind or the lambda library, which
>will create a big hassle for users.

I certainly don't like it, but when you're in the functor domain you
can often get by with using an anonymous functor. And clients can add
operator== functions easily enough...

   -- Jesse


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