|
Boost : |
From: Karl Nelson (kenelson_at_[hidden])
Date: 2000-12-06 15:09:37
[...]
> Let me just check we are talking about the same thing here, focusing on
> a straight zero arg version:
>
> template<typename result_type>
> class any_function
> {
> ...
> private:
> class body
> {
> public:
> virtual ~body_base();
> virtual result_type call() = 0;
> ... // cloning or counting features, as necess
> };
>
> template<typename adaptee_type>
> class adapter : public body
> {
> public:
> virtual result_type call();
> ...
> adaptee_type adaptee;
> };
>
> body * callee;
> };
What is coded above is
Cloned:
_____ ____________________
| | |
handle |---->| adaptor 2 |
_____| | __________________ |
|| adaptor 1 ||
|| ________________ ||
||| body |||
||| ______________ |||
|||| user functor ||||
|||| ||||
|____________________|
Counted:
_____ ____________________
| | |
handle |---->| adaptor 2 |
_____| | __________________ |
|| adaptor 1 ||
|| ________________ ||
||| body |||
||| (counter) |||
||| ______________ |||
|||| user functor ||||
|||| ||||
|____________________|
> The only difference between the two variants at this level is that
> cloning requires one more vtable entry than the reference counting
> version and the reference counting version, which uses a directly
> counted body, requires extra space per body instance for a counter. They
> are otherwise pretty much the same in terms of their impact on system
> resources, with roughly the same code overhead and roughly the same
> working set for a single instance. The difference becomes apparent with
> copying, but the point is that basic structure is not dissimilar.
Under both the counted and the cloned you do need a
body::body(const body&) and an adaptor<*>::adaptor(const adaptor<*>&),
inorder to form composites. Thus the only difference is on the copy.
Because of compositing you will require many variants. That is
For code like
callback<void,int> c=bind(function_functor(&foo),A);
callback<void,int> c2=bind(user_functor(),A);
You will get
adaptor_bind<A,function_body<void,int,A> >
adaptor_bind<A,body<user_functor> >
body<user_functor>
function_body<void,int,A>
The alternative is
_______ _______ _______ _________________
| | | | | | |
handle |-->| adp 1 |-->| adp 2 |-->| generic_functor |
_______| |(count)| |(count)| | (counter) |
|_______| |_______| | _______________ |
|| user functor ||
|_________________|
Which implements only
adaptor_bind<void,int,A>
body<void,int,A,user_functor>
function_body<void,int,A>
And requires no copy constructors. The cost is
an extra heap size of 1 counters, 1 pointer, and 1 vtables.
But this is offset if slot is every copied or shared.
This is a runtime size verses speed verses compile size.
The minimum size (cloned) has the worst copy and largest
potential binary size.
> >> I'm not sure that I understand what you mean by operator= being
> >> implemented lots. Both the reference-counted and cloning designs must
> >> implement operator=, but in one case clone is called and in the other
> >> the count is incremented. However, roughly the same amount of code --
> >> which is small -- will be generated in each case for operator=.
> >
> >Okay let me explain this better.... assume we have 4 levels of indirection
> >2 adaptor one of which introduced some objects, the generic functor, and
> >then the user functor (a function ptr in this case).
> >
> >(in sigc++ terms, this is a rather extreme case)
> >
> > void foo(int,A);
> > Slot1<int,int> s1=retbind(bind(slot(&foo),A(10)),1);
> > Slot1<int,int> s2;
> > s2=s1; // <-- this statement
> >
> >In the case of cloning we have to have an
> > adp_rettype<int, adp_bind<A, func_functor<void,int,A> > >::copy
> > adp_bind<A, func_functor<void,int,A>::copy
> > A::copy
> > func_functor<void,int,A>::copy
> >
> >where copy is either operator= or X::X(const X&) depending on the
> >implementation. If I call this 1000 times in a row this would be
> >significant. Further, every different set of adaptors and
> >functors will generate a new set of assignment operator or copy
> >constructor. (Basically this is a type explosion.)
>
> Right, you are working to a different set of assumptions -- and indeed
> quite a different design -- which is why we have arrived at different
> answers. There is no difference in the number of assignment operators
> generated (only one) between reference-counted and cloning versions in
> the code I outlined above.
Correct.
I should also note that with the composite design you get the same
number of mallocs as the chained design on constructions.
(just assume we have 2 types of adaptors, bind and rettype)
callback<int> c= rettype<int>(bind<int>(function_functor(&f),1),1);
Will need to alloc once for the function_functor then copy
to make the bind_adaptor composite and then alloc and copy to make the
rettype adaptor. It will then delete the other 2 copies.
Under the non-composite it allocs once for each adaptor but
it doesn't free any of them.
(not I am not trying to change you mind on designs, but instead
flush out the design choices we are making as I am implementing
sigc++ 1.1 right now with the design decisions I propose.)
--Karl
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk