[function] allocator template parameter question

One important advantage of using boost::function is that it acts like a function pointer, reducing physical coupling between compilation units. This is very much like shared_ptr. A nice feature of shared_ptr is that it has a single template parameter, T, even though diferent instances of shared_ptr<T> can use different allocators. When using boost::function, one can also provide an allocator, but unlike shared_ptr, the allocator is a default parameter of the boost::function class template. Is there a reason why this is necessary? Can't boost::function use similar technique to the one employed by shared_ptr to avoid the need for this second template parameter? Emil Dotchevski

On Jun 4, 2007, at 10:57 PM, Emil Dotchevski wrote:
One important advantage of using boost::function is that it acts like a function pointer, reducing physical coupling between compilation units. This is very much like shared_ptr.
A nice feature of shared_ptr is that it has a single template parameter, T, even though diferent instances of shared_ptr<T> can use different allocators.
When using boost::function, one can also provide an allocator, but unlike shared_ptr, the allocator is a default parameter of the boost::function class template.
Is there a reason why this is necessary? Can't boost::function use similar technique to the one employed by shared_ptr to avoid the need for this second template parameter?
You know, I never even thought about adding it. I didn't know about the shared_ptr technique when I put the allocator into boost::function, and after the C++ committee removed the allocator I didn't think about it any more. The biggest issue with moving the allocator into the constructor (like shared_ptr) is that boost::function doesn't know what type it is going to be interacting with... shared_ptr<T> knows it's 'T', function does not. However, I think we can work around this issue. It certainly would be better to have the allocator supplied in the constructor. I don't know when I would have time to implement this, although I would certainly consider adding this functionality to Boost's "function". Might you be interested in implementing it? - Doug

Emil: Interesting twist. I haven't looked at shared_ptr before, but you are aware of the Lakos allocator model? www.open-std.org/jtc1/sc22/ wg21/docs/papers/2005/n1850.pdf It essentially advocates this approach. The whole STL can be made to follow that protocol, although in that document it is advocated that there is a pure abstract base class allocator, and that polymorphic instances be passed as allocator* (in actuality, pointing to an instance of a type derived from allocator). Doug, this seems to me what you are trying to achieve, but without the Lakos model... I've never actually looked at boost:shared_ptr in detail to see how it can keep an allocator and use it in the destructor, but I just did: it works b/c the shared_ptr holds a pointer to a polymorphic representation, i.e. a base class (not a template) with a virtual destructor (and other virtual functions that must know the template arguments), such that the class template (which knows the allocator and derives from the base representation class) can release memory when the base representation is destroyed. Interesting trick, shows the power of OO+templates combined. In order to destroy your function object with an allocator passed at construction, seems like you should be using the same trick, which requires that your function object stores a pointer to a wrapper which instantiates a class template (allocator being one of the tmpl args) that derives from a base representation with a virtual destructor. As best I understand... you can use the same trick by having a boost::function base manager which has a virtual manage member function, and dispatch this call to a derived class templated by the allocator. In that way, the base manager does not know about the allocator, so function object does not need to know either. Unfortunately, I can't be interested in implementing it, but if someone with time on their hands was wondering how it's done, I hope I helped a tiny bit. In any case, it was interesting to me to (re-) learn the technique... (I think I had learned it once, but it's always worthwhile to revisit this kind of stuff). Btw, the overhead you add by this technique is a virtual function call for all management-related functions (cloning, destroying, etc.) including for functions which do not care about allocator (function pointers, member functions and functors with small buffer optimization). In fact, I think that this cost may be undesirable at several levels because it also will prevent a number of optimizations since polymorphic dispatch cuts the compiler off from compile-time resolution. So it may be best to provide two function class templates, one without and one with allocators. In passing, I think the allocator pure abstract base class (as in the Lakos-allocator model) is better in this respect: you always have an extra pointer for storing the allocator passed at construction, but the invocation is not a template nor a polymorphic call (at least not until you actually want to deallocate). So it does not prevent the optimizations. Thanks. I learned something more tonight. On Jun 4, 2007, at 11:24 PM, Douglas Gregor wrote:
On Jun 4, 2007, at 10:57 PM, Emil Dotchevski wrote:
One important advantage of using boost::function is that it acts like a function pointer, reducing physical coupling between compilation units. This is very much like shared_ptr.
A nice feature of shared_ptr is that it has a single template parameter, T, even though diferent instances of shared_ptr<T> can use different allocators.
When using boost::function, one can also provide an allocator, but unlike shared_ptr, the allocator is a default parameter of the boost::function class template.
Is there a reason why this is necessary? Can't boost::function use similar technique to the one employed by shared_ptr to avoid the need for this second template parameter?
You know, I never even thought about adding it. I didn't know about the shared_ptr technique when I put the allocator into boost::function, and after the C++ committee removed the allocator I didn't think about it any more.
The biggest issue with moving the allocator into the constructor (like shared_ptr) is that boost::function doesn't know what type it is going to be interacting with... shared_ptr<T> knows it's 'T', function does not. However, I think we can work around this issue. It certainly would be better to have the allocator supplied in the constructor.
I don't know when I would have time to implement this, although I would certainly consider adding this functionality to Boost's "function". Might you be interested in implementing it?
- Doug _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/ listinfo.cgi/boost

On 6/5/07, Hervé Brönnimann <hervebronnimann@mac.com> wrote:
[...] Btw, the overhead you add by this technique is a virtual function call for all management-related functions (cloning, destroying, etc.) including for functions which do not care about allocator (function pointers, member functions and functors with small buffer optimization). In fact, I think that this cost may be undesirable at several levels because it also will prevent a number of optimizations since polymorphic dispatch cuts the compiler off from compile-time resolution.
Aren't these operations (cloning, destroying, invocation, etc.) already currently implemented using a set of function pointers (at least they were last time I looked)? I do not think there is any significant performance difference between a call through a function pointer an though a virtual function (may be a couple extra instructions to fetch the address from the vtable). The biggest performance hit with both function pointers and virtual functions is the fact that is very hard for the compiler to inline them and thus require a badly predictable indirect jump. I wouldn't be surprised if compilers were better at inlining virtuals than pointers. Anyways, I do not think that the allocator trick require virtual functions, probably the existing type erasure can be used to also accommodate the allocator.
So it may be best to provide two function class templates, one without and one with allocators.
Oh, no please! What makes boost::function (and shared_ptr btw) very good is the "one type fits all" design that allows it to be passed between unrelated libraries. Let's keep it that way. gpd

On Jun 5, 2007, at 5:42 AM, Giovanni Piero Deretta wrote:
On 6/5/07, Hervé Brönnimann <hervebronnimann@mac.com> wrote:
[...] Btw, the overhead you add by this technique is a virtual function call for all management-related functions (cloning, destroying, etc.) including for functions which do not care about allocator (function pointers, member functions and functors with small buffer optimization). In fact, I think that this cost may be undesirable at several levels because it also will prevent a number of optimizations since polymorphic dispatch cuts the compiler off from compile-time resolution.
Aren't these operations (cloning, destroying, invocation, etc.) already currently implemented using a set of function pointers (at least they were last time I looked)?
Yes, these operations already have to be implemented using function pointers, because boost::function does not know statically what type of function object it will store. So, making the allocator dynamically change just means moving the allocator itself into the same place where the function object is stored: there will be no additional performance cost.
Anyways, I do not think that the allocator trick require virtual functions, probably the existing type erasure can be used to also accommodate the allocator.
Yes.
So it may be best to provide two function class templates, one without and one with allocators.
Oh, no please! What makes boost::function (and shared_ptr btw) very good is the "one type fits all" design that allows it to be passed between unrelated libraries. Let's keep it that way.
Right. - Doug
participants (4)
-
Douglas Gregor
-
Emil Dotchevski
-
Giovanni Piero Deretta
-
Hervé Brönnimann