|
Boost : |
From: Douglas Gregor (gregod_at_[hidden])
Date: 2001-11-17 13:51:05
On Saturday 17 November 2001 12:25 pm, you wrote:
> 0. Am I missing something??
Looks like you've characterized the problem very nicely.
> 1. What was the design rationale behind prefering the possibility to
> adapt "foreign" function objects over the possibility to use
> boost::function in publish-subscribe schemes? IMHO, the latter is
> probably needed by more programmers.
When one calls, for instance,
std::sort(first, last, less<long>());
the actual type of the function object is irrelevant. It only matters that
the function object can be invoked as f(a, b) where a and b are convertible
to long, and the result is usable in a boolean context. It's a tremendous
amount of flexibility that C++ programmers have become accusomed to.
We wanted that same flexibility when the actual type that is passed in _does_
matter. For instance, we want to store that arbitrary object, or pass it to
a routine that cannot be templated over the function object type (e.g., a
virtual function). That's where Boost.Function comes in: it takes the
flexibiliy afforded by function objects whose types are template parameters
to the receiver and makes it available where we can't make the type a
template parameter.
I don't believe that allowing "foreign" function objects precludes the
possibility of using boost::function in publish-subscribe schemes. In fact,
I'd like to claim that the implementation you present for publish-subscribe
is the "wrong" way to implement publish-subscribe, but I'll get back to that
later...
Regarding your last comment, that publish-subscribe is needed by more
programmers; I don't think I agree. Publish-subscribe is extremely useful for
so-called "events" to "signals" (depending on whose terminology one uses),
and I'll agree that this these are used often in many programs. However, I
think perhaps we are ignoring more subtle uses of boost::function that
require the ability to "adapt" function objects.
Take, for instance, the Boost Threads library. One starts a thread by passing
an arbitrary function object to the "thread" class. The constructor taking a
the arbitrary function object could be implemented several ways:
1) Have a template parameter for the function object type. This would allow
maximal flexibility in what the function object can be: it must be callable
with zero arguments and the return value, if any, will be ignored. However,
this could cause code "bloat", because for each different function object
type there will be a different copy of the (possibly large) constructor.
2) Take a (member or free) function pointer: less flexible and does not
have state. However, it doesn't cost much in terms of code size.
3) Take a pointer to a class derived from some "thread_function" abstract
class that contains a virtual function to call through. This is flexible, but
is much less clean when creating this subclass.
4) Take a boost::function object. It allows the same flexibility of 1 & 3,
with the same syntax as 1, but with a much smaller code "bloat" factor. The
constructor itself is not a template so only one copy of that code will be
needed. There is a small amount of code generated for the thunk to call
though the boost::function object to the actual object stored, but that is
generally very small.
In summary, boost::function is designed to make it possible to transform
routines that take arbitrary function objects as parameters from routines
that are templated over the types of those function objects to routines that
don't need function object type template parameters.
template<typename F> void my_routine(F f) { z = f(...); }
to
void my_routine(boost::function<...> f) { z = f(...); }
> 2. As mentioned above, there seems to be no way to extend
> boost::function, boost::bind and boost::mem_fn to provide equality
> operations AND automatic conversions from "foreign" functors. What's
> worse, with equality operations, the functionality of boost::function
> and boost::bind seems to be no longer orthogonal. Any ideas anyone?
Agreed. I also don't think it would be correct to compare functions (or
function objects). I've made the argument before, so I don't want to rehash
all of it, but basically comparison of functions is undecidable. You can't
even attempt it in C++, much less solve it. You can, of course, limit your
ability to have a subscriber be _any_ type of function object to get down to
a decidable subset, but then you start coding yourself into a corner and you
become very reliant on the specifics of the publish-subscribe mechanism.
I think that the "correct" way to write a publisher/subscriber mechanism is
either what Peter proposed (by explicitly naming each subscriber) or by
returning a token of some sort to use in removal:
connection AddSubscriber(boost::function<...>);
void RemoveSubscriber(connection);
Then you don't ever need equality of functions, so you need not restrict
yourself to a specific subscriber interface.
There's a practical side to this as well. Using the original formulation of
AddSubscriber/RemoveSubscriber that relies on comparison of subscribers, you
always have to pair AddSubscriber/RemoveSubscriber calls. While that may be
trivial if you are just passing in member function pointers, it is much
tougher to duplicate a function object that was constructed using, .e.g.,
boost::bind. Function object comparison makes things tricky for everyone.
There is a prototype of a publish/subscribe-like mechanism in the files area
(http://groups.yahoo.com/group/boost/files/Callback/sigslot.tgz), but it has
not been updated for quite a while. Now that two people have asked about such
a library in as many days, perhaps I should resume serious work on the
library. If you are interested, perhaps you will want to take a look at the
interface provided in the above prototype.
Doug
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk