|
Boost : |
From: Karl Nelson (kenelson_at_[hidden])
Date: 2001-12-27 12:40:16
> On Thursday 27 December 2001 01:30 am, you wrote:
> > Because some the chief arguments seems to be included in this
> > library being proposed. The use of numbers after the signal
> > names was one of the key complaints cited when I proposed sigc++
> > to boost. Obviously these arguments are dropped some place!?
>
> Not dropped at all. The numbered variants exist for binary compatibility and
> to allow users to reduce the amount of code that needs to be compiled (e.g.,
> by including only the header for a signal/function with 2 arguments). The
> unnumbered classes still exist, and only day the committee may decide that
> template typedefs should be added to the language and then the binary
> compatibility argument will no longer hold. For now, the best we can do is
> have signal (or function) derive the appropriate signalN (or functionN) class
> and reimplement some functionality that won't be found because of lookup
> rules.
This was a major argument cited when I last proposed SigC++ and
the argument I gave at the time was basically what you just gave
above. Thus that objection has been dropped.
> > > This, for me, is the main reason I believe that SigC++ does not belong in
> > > Boost. Why should a signals & slots library contain code for binding
> > > arguments in calls to member functions? Why should it contain adaptors to
> > > perform type conversions on slot call arguments when the types don't
> > > match exactly? There are libraries that do this type of operation - Bind,
> > > Lambda, FACT!, FC++, Spirit semantic actions, etc. - and do it better
> > > than we can expect a signals & slots library to do it, because a signals
> > > & slots library isn't about argument binding or function composition at
> > > all.
> >
> > Because as always stated, tracking of elements must run through the
> > adaptors. The signal code proposed does not address this as far
> > as I can tell.
>
> It does. The visit_each free function applies a visitor to all bound objects
> within a function object. Boost.Bind implements the visit_each functionality,
>
> and other libraries can be adapted to do the same. There are many other
> reasons one may want the ability to visit all subobjects (e.g., garbage
> collection or persistence), so I would expect that "visit all subobjects"
> functionality will become more common; why restrict such a useful thing to a
> signals & slots library?
The visit each mechanism seems grossly specific to signals in your
implementation. trackable objects shoiuld have the ablity to place
a dependency with clean up info which can be removed at a later point.
It should not be specific to signal/slots. I can find no mention
of the mechanism anywhere in your docs.
Can you describe clearly how some alternate implementation of a
signal, or just a simple slot which binds to object which is freed
would plug into your mechanism?
> > If you construct a functor from a trackable element
> > using any of those other libraries you name, you lose the ablity to
> > track the object.
>
> If they do not implement visit_each, yes.
Then your argument about how SigC++ would require modification
of lambda would require modification is pointless. Every library
in boost which needs your tracking will need your tracking
modification as well!
> > Any good signal/slot library will need to be
> > integrated with the binding of objects/methods and callbacks.
> > Doing it any other way greatly increases the code bloat.
>
> Not necessarily. There need be no difference between the sizes of the
> following when the Callback type is unchanged:
>
> template<typename R, typename Callback = Default> class signal0;
> template<typename R> class signal0;
>
> The overall program size only changes when signal0<R, my_callback> and
> signal0<R, her_callback> have much of the same code that doesn't depend on
> the callback type. The solution is generally to factor out the common
> functionality into a base class. There are more radical solutions. For
> instance, here is is an example of the Signals library:
>
> The Signals library supports named slots, and the type of the name is
> user-configurable. The signal0 definition looks something like:
>
> template<typename R, typename SlotName = std::string,
> typename SlotNameCompare = std::less<SlotName> >
> class signal0;
>
> Internally, the slot names are stored in a std::map, so this can result in
> massive code bloat when a user changes the SlotName parameter (a new std::map
> is instantiated, and those are huge). This can still be factored using a form
> of type erasure. The "signal_base" class actually contains a named slot
> mapping that looks like this:
>
> std::map<boost::any, boost::connection,
> boost::function2<bool, boost::any, boost::any> > named_slots;
Naming of slots as you have given above seems less like a necessary
feature of a signal than some extra bloat tacked on. What likely
use will it have and how often do you see it employed?
[...]
> > And worse overly complicates the code.
>
> Absolutely not. The code becomes simpler because the usage of, for instance,
> a callback type is trivial. There are only two major operations on the
> callback type:
> - construct from a slot/function object
> - call with a given set of arguments
>
> The difference is that a "connect" method might look like:
> template<typename Slot> void connect(const Slot& slot);
>
> instead of:
>
> void connect(const slot_type& slot);
There is a huge difference between those two statements. slot_type
can be in a virtual function, passed up or down the function stack
and easily stored on a list<slot_type>.
> > Growing each concept separately, often creates a large set of not
> > entirely integrated peices which in turn lead to horrible bloat and
> > inefficency.
>
> If the underlying concepts are poorly specified, then perhaps bloat and
> inefficiency would be the result. Callback and function object concepts are
> quite trivial, and have been used efficiently for a quite a while. Why would
> they fail here?
Could you cite a specific large program implemented with these concpets
and give relative performance measures you used to measure efficency?
> > I think your goal is not exactly what should be that of boost. Is it
> > the point of a standards orginization to simply hand down standards which
> > someone arbitarily feels is best or should it be adapted from those
> > items which are found in industry to already work well?
>
> Existing practice is an important thing for sure, but standards bodies should
> also be forward-thinking: will the restrictions of the "existing practice"
> limit users in the future? What's the point of having a great binding library
> if you can't use it with signals & slots?
How can one be forward thinking when one does not look at the past??
You have skimmed the sigc++ library, but not used it in any apprecable
way. You have not spoken with the users on the SigC++ list (300 of
them) and asked what things work and what things don't in SigC++.
SigC++ is hardly perfect and may not be appropraite for boost, however
you seem to make that judgement alone.
> > I am not saying
> > fresh code is not needed, but you seem to really be reinventing the wheel.
> > Considering most of the interface (signals method name, connection
> > concept) were taken from sigc++ why not adapt the whole thing rahter
> > than such peice meal.
>
> I'm trying to generalize the wheel - take away the reliance on concrete
> classes and specific implementation behavior, and look at signals & slots
> from the abstract point of view.
I don't know how ell this has been acheived. Unlike list, vector,
where I can see alternative implementations your code seems pretty much
one way. Though SigC++ is hardly a good model of allowing alternative
implementations.
> I looked at what was available - SigC++, Qt,
> .NET delegates, and a few other libraries - and concluded that abstracting
> the existing code would require much more work that starting anew. Other
> benefits of starting from a new code base include the ability to be _very_
> careful with regard to exceptions, because signals & slots systems are very
> fragile in the face of exceptions. I wasn't able to convince myself that any
> of the C++ libraries out there had been designed with exception safety in
> mind.
SigC++ has have at least some expection thought put into it. I
have tested which exceptions but I still do not claim full safety
at I need more auditing.
> If an exception is thrown while connecting a signal to a slot, will the
> signal and slot be left in a working state? Say I'm connecting a signal "sig"
> to a slot "slot", where "slot" contains two trackable objects, and in trying
> to allocate memory to store the connection object for the second trackable
> object an exception is thrown. Then we have the first trackable object
> thinking that it is connected to the signal, but the second isn't; perhaps
> "sig" thinks its connected to the slot and perhaps not. In any case, our
> connection is not complete and either the "sig" object or the first trackable
> object is left in an inconsistent state. Has this been considered for SigC++,
> because it isn't documented AFAICT?
Yes it had been considered. Which is why it uses a smart pointer
class extensively. However, given that SigC++ was written to
support a library which can't throw exceptions (because of the
call stack merging with other languages) I can't say I have
given it its due consideration.
> > > Orthogonality in library design allows rapid innovation.
> >
> > I don't see the other one as anything other than different implemention
> > without some of the major features. Tracking of dependencies, ablity
> > to adapt while maintaining those and a slot concept which can be
> > grown are all part of sigc++. It seems a fairly orthagonal set.
>
> It's an orthogonal set conceptually, yes, so why is it in a single library?
> By orthogonal I mean that each piece of the puzzle is a separate entity that
> performs a single, well-defined task. The pieces can be assembled to create a
> unified signals & slots system. If one piece breaks, or some genius comes
> along with a better way to perform one of those tasks, we can just swap out
> the old piece and replace it with the new piece.
In the end it must be a single library. To do so in the generalized
fashion just hasn't made a very practical industrial library. But
it doesn't seem you want practical. I have looked several times at
lambda and hardly feel it bears any resemblence to C++. Yes it is a
wonderfully but my point is you are talking about
components which don't see a whole lot of use in industry.
Containers should be standardized as they are implemented over and over
throughout current industry. Streams same. Smart pointers, definitly.
Signals/slots probably at this point given the number of implementations
now out there. Lambda? I have no experience which libraries like that
and how much use they get.
[...]
> > Lack of adaptors. SigC++ provides....
> [I'm reordering and grouping these]
>
> > bind_return - give a return value to a void slot.
>
> This could be a useful addition to <functional> - why is it in a signals &
> slots library, when it can be used with any algorithm or data structure that
> uses function objects?
>
> > bind - place a value onto the last argument of a slot.
> > chain - call one slot with the return of another.
> > hide - add an used parameter to a slot.
> > convert - add intermedate function to a slot.
> > slot (methods, object+method, function)
> > - create unified slot type to bind all those elements
> > with tracking.
> > closure (obj in slot+method)
> > - functor slot.
>
> Boost.Bind can do all of these, with a unified syntax, as can several other
> binding/composition libraries.
> What if I want to trap a type of exception?
convert.
> Or use operator notation on my slots?
closure.
> > class_slot (object+method) - slot without tracking.
> > auto_disconnect - disconnect after a specific number of calls.
>
> These aren't covered by any binding library, because they are the result of
> merging the argument binding and signals & slots libraries. They can easily
> be added: class_slot is just a function object adaptor for which visit_each
> is specialized to do nothing, and auto_disconnect can be implemented as an
> adaptor containing a call count and a connection.
If you didn't know these adaptors existed and you are forward thinking,
then how long did you look at existing practice??
> > Providing this set with the boost::signal implementation or trying to
> > pull them in piecemeal will give poor results. SigC++ draws rave
> > reviews from its users who frequently tell me it provides everything
> > they need so much so that when they discover "gee it would be nice to
> > have this" most I the time I point out it is already there.
>
> And we'll point out how it can be done with a Boost library. That knowledge
> will carry on to other tasks that have nothing to do with signals & slots.
> Say I've learned how to chain/compose slots in SigC++ - does this help me
> with function composition when calling std::transform? If I've learned
> chaining/composition using the Boost Bind or Compose libraries, I can
> immediately apply that knowledge to Signals and to std::transform.
>
> Would you argue that std::vector should have a sort() method, so that it is
> more complete?
Now you are just trying to shove words I may mouth and make me defined
positions not my own.
> > The reason is you are implementing the same system which I went
> > away from when I converted Tero's code. That is requiring the
> > Signal to know something about the Slot.
> >
> > signal.connect(functor())
> >
> > implies that functor has operator()() of the type used by signal
> > and the signal gives it this info.
> >
> > Now lets try to place an adaptor in the path...
> >
> > signal.connect(adaptor(functor());
> >
> > adaptor must implement an operator()() for a connection type
> > which is not known.
>
> visit_each solves this. The adaptor need not know anything about the
> connection.
You missed the point entirely. How is adaptor supposed to know
what method to pull out of functor? Does it simply implement
every operator()(....) for arguments 1 through 10?
> > Thus sigc++ is really more orthagonal as
> > the tools were meant to work together.
>
> Signals is meant to work with Bind, with Function, and other libraries as
> well. std::sort is meant to work with std::vector, but <algorithm> doesn't
> include <vector> and <vector> need not include <algorithm>. They're loosely
> coupled through the RandomAccessIterator concept, and they work very well
> together. So long as Bind fulfills its requirements (by generating a
> CopyConstructible and Visitable function object), and Function fulfills its
> requirements (by constructing itself from a CopyConstructible function object
> and being able to call that function object through operator()), the Signals
> library will work well with both of them. So will Compose, or <functional>,
> or SigC++ argument binding, or function pointers...
>
> > > Suggestions always welcome. Signals & slots seems to be the "popular"
> > > adopted name, and thus I chose to keep it even though slots are not
> > > concrete entities .
> >
> > I considered dropping signal name myself, but as I have an actual slot
> > concept it makes sense. Implementing signal by itself (without the slot)
> > just makes it too confusing. It should be a function_list or something
> > like that.
>
> Signals has a slot concept.
>
> > The concept of signal/slot is simply any type of function/functor/method/
> > closure goes into a unified slot interface and that is what signal accepts.
>
> ... and the only difference in the unified slot interface is that SigC++ has
> the word "slot" but Signals doesn't? I fail to see the conceptual difference
> between sig.connect(slot(function_obj)); and sig.connect(function_obj).
A signal class with a hidden "slot" concept would best be named something
other than "Signal". Signal is a Threading concept, an IPC concept
in unix, and one half of a signal/slot implementation. SigC++ calls it
signal only because it is being used in a context where the first two
are not likely to be seen (widgets). Making it a general library
name in a standard would be poor practice. It was a bad name to begin
with and SigC++ carried it too far.
--Karl
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk