I l@ve RuBoard Previous Section Next Section

5.13 Real-World Issues II: Heap Allocation

Let's concentrate now on the cost of constructing and copying Functors. We have implemented correct copy semantics, but at a cost: heap allocation. Every Functor holds a (smart) pointer to an object allocated with new. When a Functor is copied, a deep copy is performed by using FunctorImpl::Clone.

This is especially bothersome when you think of the size of the objects we're using. Most of the time, Functor will be used with pointers to functions and with pairs of pointers to objects and pointers to member functions. On typical 32-bit systems, these objects occupy 4 and 20 bytes, respectively (4 for the pointer to an object and 16 for the pointer to a member function[8]). When binding is used, the size of the concrete functor object increases roughly with the size of the bound argument (it may actually increase slightly more because of padding).

[8] You might expect a pointer to a member function to occupy 4 bytes, just as pointers to functions do. However, pointers to methods are actually little tagged unions. They deal with multiple virtual inheritance and virtual/nonvirtual functions.

Chapter 4 introduces an efficient small-object allocator. FunctorImpl and its derivatives are perfect candidates for taking advantage of that custom allocator. Recall from Chapter 4 that one way to use the small-object allocator is to derive your class from the Small-Object class template.

Using SmallObject is very simple. We need, however, to add a template parameter to Functor and FunctorImpl that reflects the threading model used by the memory allocator. This is not troublesome, because most of the time you'll use the default argument. In the following code, changes are shown in bold:



template


<


   class R,


   class TL,


   template <class T>


      class ThreadingModel = DEFAULT_THREADING,


>


class FunctorImpl : public SmallObject<ThreadingModel>


{


public:


   ... as above ...


};


That's all it takes for FunctorImpl to take full advantage of the custom allocator.

Similarly, Functor itself adds a third template parameter:



template


<


   class R,


   class TL,


   template <class T>


      class ThreadingModel = DEFAULT_THREADING,


>


class Functor


{


   ...


private:


   // Pass ThreadingModel to FunctorImpl


   std::auto_ptr<FunctorImpl<R, TL, ThreadingModel> pImpl_;


};


Now if you want to use Functor with the default threading model, you don't have to specify its third template argument. Only if your application needs Functors that support two or more different threading models will you need to specify ThreadingModel explicitly.

    I l@ve RuBoard Previous Section Next Section