Boost logo

Boost :

From: Karl Nelson (kenelson_at_[hidden])
Date: 2000-12-05 18:22:55


[...]
> > > Copying isn't always small. In fact, it can be much more
> expensive
> > > than the cost of a lock. This shows that this level of micro-
> > > management of optimizations may not be appropriate. Not that I
> > > totally disagree with what you're saying here, I'm only pointing
> out
> > > that you've not got a clear case for choosing one idiom over the
> > > other here.
> >
> > This again depends on the frequency and the specifics of the system.
> > Potentially, cloning can be a malloc every copy which may be very
> > expensive. But then if you end up paying for reference counting
> > then potentially the cloning can be quite cheaper. I generally
> > use benchmarking to determine tradeoffs like this.
>
> The problem with benchmarking is that you don't know what to
> benchmark. You don't know if a particular application is going to
> require several function objects that are expensive to copy or not.
> You simply can't predict this sort of thing. This is a poor place to
> optimize, IMHO.

You don't benchmark one thing. You bench mark all of them and
then decide if one factor is coming at too high of cost compared
to all the rest. You have to do this because often we don't realize
all of the transparent thing which happen behind the scenes.
(Just last night I benchmarked my bind function to discover that
when created it copied the object no less than 6 times, trivially
fixed by making some functions generate references from traits rather
then use template arguments directly.)

I benchmarked all the possible operations. Create a 1000 types of
callbacks, create a 1000 copys of one type, copy a callback millions
of times, copy millions of different types, ..... (repeat for
assign, emit, create, destroy, ... evaluate for both runtime size
and binary size.)

I did this for 5 partially completed designs until I found one which
met my design goals. This is sigc++ 1.0. It was a fairly even
mix with every operation O(1). Had someone not requested a copy
operation for slots I would likely call it final.

[...]
> > This covers another signal thread case that is self assignment. If
> > a callback calls something which causes that callback to change (
> > assignment) when the callback is still on the stack, it will
> > cause the system to die when the stack unrolls.
>
> This sort of self assignment may be something you've run into in your
> GUI applications of sigc++, but in general it's a corner case that
> would have several work arounds and so would be best left as
> undefined behavior IMHO. To me, this is akin to "delete this" in C++.
>
> > class my_functor
> > {
> > A a; // some data
> > operator()
> > {
> > do_something();
> > a++;
> > }
> > };
> >
> > Functor f=my_functor();
> >
> > void do_something()
> > {
> > ...
> // Simple fix outside of the library
> Functor temp = f;
> > f.clear();
> > ...
> > }
> >
> > main()
> > {
> > f();
> > }

You missed the point of the example. do_something cleared "f"
precisely because they don't want that functor called again. Using
a local copy does nothing but make the code compile without fixing
the functionality to what the user actually desired. The
fact the user called do_something which ended up linked inside
the f was not necessary visible. This user would inevitably send
me an email saying SigC++ managed to segfault for no apparent reason.
After staring at countless backtraces, I declared it a frequent
operation. :-)

It is common operation in a GUI for an action to trigger something
once. This amounts to calling the action which then removes itself
from being called again. (since the parent would otherwise call the
action over and over every time the event occurs.)

> > This case turned out to be fairly common in GUI systems. :-(
> >
> > A button in a dialog closes that dialog, thus pressing the
> > button emits a signal which kills the dialog which destroys the
> > button, which blanks the signal which is currently emitting, which
> > clears the slot which now has a reference count of zero, but
> > we will access that data again when we roll the stack back.
>
> Definately sounds like a "delete this" case to me. If the button is
> destroyed during the "emit" you could have a lot more problems than
> just the function object going away while it's running on the stack.

GUI programming seems to encourage "delete this". Don't ask me why!
But no you don't need to actually delete to get things winding back and
doing assignment of things on the stack.

 
> > A = we have a reference, so the execution doesn't need one
> > B = the callback can be hooked back to effect the original object
> >
> > conflict!
> > A----><----B
> >
> > Note, cloning doesn't help this problem and in fact makes it worse
> > because any assignment of the callback assures the current callback
> > is destroyed. So cloning boost system would not be appropriate for
> > sigc++ style multi-cast. Thus my rather long discussion on why
> > boost can't just hope to build the ideal simple system and then
> > patch it over to allow the dependency tracking and multi-cast. It
> > just won't work.
>
> I don't really buy this. "Patching over" may not, but wrapping in a
> higher level concept should. Whether that's the best implementation
> for this different concept is debatable, but claiming it's not
> possible doesn't sound believable.

Okay, sure it is possible, but the effort and quality of system
which is results would compromise most of what you are trying to
achieve. If in your cloning system you had to allow for self
assignment from within a callback you would need counters, which
would defeat the point of cloning now wouldn't it? :-)

> > Either we embrace large amounts of safety at cost of speed and
> > memory or you don't. There really isn't a good in between.
>
> I'm not sure I buy this either, though it's mostly based on my belief
> that you can wrap a fast implementation into a new concept that's got
> the safety you want.
>
> > > Do you ever pass a
> > > pointer to a shared_ptr<> object? Even though this theoretically
> > > could occur I think it would be more appropriate to document this
> as
> > > undefined behavior instead of over coding like you've done
> above.
> > > After all, this is consistent with what you have for shared_ptr
> today.
> >
> > I know the above case looks nasty, but this is what happens if you
> > really are trying to get safety with lifetime safe signals. And
> under
> > that case it is necessary.
>
> That depends on the level of safety you're trying to achieve. You
> need to be careful to not go too far with safety checks. In the
> example you gave with the button I think you have. The function
> being called has to know that once emitted the button will be
> destroyed, other wise any use of references to the button will result
> in undefined behavior. If the programmer knows this at that point,
> it's trivial for him to insure the function object isn't destroyed
> until afterwards, and that will increase efficiency for all other
> cases.

In an anonymous system, you can't check to see where the target is
and thus creating loops with break the assumptions is easy to do
and impossible to see. Multicast exaggerates the problem as one
source can place a callback on the list and later another action
gets places on the same list which breaks assumptions, and since
neither of those actions is dangerous alone the conflict is hard
to track. I admit sigc++ is on a rather far extreme,
but then it as to be as it intends safety over memory size and speed.

> The real danger here is that a programmer may not know that emitting
> will cause destruction... but attempting to make the signal/slot
> safer in this regard just makes it more likely that the programmer
> won't realize this and will do something wrong.

Wrong is subjective. :-)

[...]
> I agree that it's tricky to manage this stuff. I just don't agree
> you came up with the right solution by upping the ref-count during
> execution of the signal.

Well, I do use other methods to prevent self assignment. Since
a signal has exclusive rights all the nodes immediately underneath
it, it is the only one that needs to track executions. It then
defers requires to disconnect these objects until the execution count
reaches zero.

I likely won't tackle self assignments for slots as direct emission of
slots is rare. Even I get tired of the extreme paranoia that a
signal/slot system would have to have to be completely safe.

--Karl


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