Topic: Closures again :) (long)
Author: Don Griffin <dgriffin@no.spam.farallon.com>
Date: 1997/09/02 Raw View
Richard,
Thanks for taking the time to reply. Bob Steagall pointed me to an
alternate implementation (by Rich Hickey) that is much better than what
I posted. It removes the virtual method from TFunctorX and provides
pass-by-value semantics for the closures (which is what I really
wanted).
> How about specifying the interface you would want to be able to use the
> closures? Then it would be possible to see how close we can get
> currently, and how much overhead the compiler would be able to remove.
>
My ideal syntax would be:
Declare a closure type:
typedef void (class::*PEvent) (int);
Create a closure:
class TFoo {
public:
void OnEvent (int);
};
PEvent ev = closure <this, OnEvent>;
The type of "this" is used to determine which class OnEvent comes
from. And
from inside a non-static TFoo method, the "this" parameter can be
implicit
in the closure:
ev = closure <OnEvent>;
What I have come up with provides this syntax:
Declare a closure type:
typedef TFunctor1<void,int> PEvent;
Create a closure:
PEvent ev = closure (this, &TFoo::OnEvent);
I decided not to use a macro for the implicit "this" form of closure,
nor could I remove "&TFoo::" to take the address of the method. As it
turns out, to pull off this clean syntax, I need partial template
specialization to handle "void" returns. Or, I could use "closure" for
non-void returns and "closure_v" or something for void returns.
> It seems to me that most of the price is inevitable, and getting the
> compiler to implement the workings instead of a library writer wouldn't
> make it go away. Neither do I find it too cumbersome.
>
The size of a TFunctor1<void,int> using VC++5 is 20 bytes. I imagine
that most other compilers would have 16, since VC5 uses 16-bytes for
method pointers and most other compilers use only 12.
I think that the compiler could create 8-byte closures with almost no
overhead beyond that of a normal function pointer. The price for this?
Move the cost of determining the addresses to the creation of the
closure.
Using traditional method pointers, the compiler does not know the class
of the object on which the pointer will be used, and hence must store
extra information to make the call properly. However, with a closure,
the compiler has object and method in hand and could resolve them to the
proper value for "this" and the true 4-byte address for the method to
call. This tradeoff is probably well worth it, because an event driven
system based on closures will make many more calls through closures than
creations of closures. Certainly, the compiler has helper functions
already in place to do this kind of thing, so this optimization doesn't
sound too difficult.
Using templates I can get most of what I want, but the perfectionist in
me would very much like to see closures (and properties, but that's
another discussion<g>) in the language.
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: Richard See <Richard.See@vega.co.uk>
Date: 1997/08/28 Raw View
Don Griffin wrote:
>
> I have implemented one of the suggested techniques to emulate the
> concept of a closure, and I have some questions for anyone familiar with
> the topic.
>
> My code:
...
> I see two scenarios for using these for events:
>
> #1 - Dynamically allocate the closure and keep a pointer to it in the
> object that wants to fire the event.
>
> #2 - Declare the closure as a member of the object with the event
> handler.
>
>
> My problems with this approach are:
>
> 1. Both #1 and #2 have a lot of overhead.
I didn't think that #2 had much overhead. #1 had an extra new/delete of
an object, that might not be too bad either, depending on your
application. There is opportunity to use an optimised memory
allocation, since the closures will tend to be the same size.
> 2. Since the size and behavior is dynamic, I must deal with
> TFunctor*'s and not a closure instance.
You could have wrapped them in a reference counting interface.
> 3. There is a lot of syntax to setup the closure.
It didn't look too bad to me in case #1.
> 4. There are lots of templates involved. Two for 0-arguments (1 for
> 0-args and void return, and 1 for 0-args w/non-void return), two for
> 1-arg, etc.. By the time you get setup for 0-3 arguments you have 8
> templates.
There are, but you could always stick to one argument, and pass a struct
if you want to pass more than one item of data.
> 5. I made the dtor virtual because the closure objects get deleted thru
> the base class pointer.
In case #1 they do. What % performance hit will making the dtor virtual
have on your system? I imagine it would be small compared to the memory
management overhead.
> Are there any improvements that someone could suggest?
Add reference counting to make the interface simpler.
> If this is the best that the language can do, I want to re-assert my
> strong preference
> for a closure in the language. I realize it's too late for me to get
> it, but this set of templates is too cumbersome and pricey to be used
> for events.
It seems to me that most of the price is inevitable, and getting the
compiler to implement the workings instead of a library writer wouldn't
make it go away. Neither do I find it too cumbersome.
How about specifying the interface you would want to be able to use the
closures? Then it would be possible to see how close we can get
currently, and how much overhead the compiler would be able to remove.
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: Colin Rafferty <craffert@ml.com>
Date: 1997/08/28 Raw View
Don Griffin writes:
> I have implemented one of the suggested techniques to emulate the
> concept of a closure, and I have some questions for anyone familiar with
> the topic.
> My code:
> [edited for brevity]
> class TFunctor_V0
> { ... };
> template <class T>
> class TClosure_V0 : public TFunctor_V0
> { ... };
> I see two scenarios for using these for events:
> #1 - Dynamically allocate the closure and keep a pointer to it in the
> object that wants to fire the event.
> #2 - Declare the closure as a member of the object with the event
> handler.
Choice 1 seems better if you need to keep it around. The event handling
object (or a class that knows about the handler and the caller) should
be creating the initial closure. This way, the handler does not need to
know that it is involved with a closure.
> My problems with this approach are:
> 1. Both #1 and #2 have a lot of overhead.
The overhead of #1 is that the caller must have a single pointer to the
closure. It needs to do this anyway. The other overhead is the memory
of the closure itself. In your implementation, this exists explicitly.
If the compiler did it for you, it would be implicit, but the overhead
would still be there.
> 2. Since the size and behavior is dynamic, I must deal with TFunctor*'s
> and not a closure instance.
But they are the same -- they just have different names.
> 3. There is a lot of syntax to setup the closure.
> a->setEventHandler (new TClosure_V0<B> (this, &B::onAEvent));
Not if you create helper functions the return a closure:
template <class Actor> std::auto_ptr<TFunctor_V0>
make_closure(Actor* actor, void (Actor::* func)()) {
return TClosure_V0<Actor> (actor, func);
}
a->setEventHandler (make_closure (this, &B::onAEvent));
> 4. There are lots of templates involved. Two for 0-arguments (1 for
> 0-args and void return, and 1 for 0-args w/non-void return), two for
> 1-arg, etc.. By the time you get setup for 0-3 arguments you have 8
> templates.
But only in the header files. And since all the helper functions can be
identically named, from the point of view of the user, there is only one
function call.
> 5. I made the dtor virtual because the closure objects get deleted thru
> the base class pointer.
This is the correct thing to do, since you already have a pure virtual
function (Invoke).
> Are there any improvements that someone could suggest?
Most of my ideas I got from the "function" section of the standard
library. I would suggest following some of its methodology.
> If this is the
> best that the language can do, I want to re-assert my strong preference
> for a closure in the language.
I guess I don't see where the language would have a simpler method for
the user of closures. The only real burden is in correctly implementing
closures. Once you do that, you can just use them.
> I realize it's too late for me to get
> it, but this set of templates is too cumbersome and pricey to be used
> for events.
I stongly disagree with this statement. The complexity of using them
can be no worse than if they were built-in. The overhead should be no
more than if they were built-in.
--
Colin Rafferty
---
[ comp.std.c++ is moderated. To submit articles: try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]