Boost logoBoost.Flyweight Tutorial: Extending Boost.Flyweight



Contents

Introduction

Boost.Flyweight provides public interface specifications of its configurable aspects so that the user can extend the library by implementing her own components and providing them to instantiations of the flyweight class template.

In most cases there are two types of entities involved in extending a given aspect of Boost.Flyweight:

For example, the type static_holder is a holder specifier which is used by flyweight to generate actual holder classes, in this case instantiations of the class template static_holder_class. Note that static_holder is a concrete type while static_holder_class is a class template, so a specifier can be seen as a convenient way to provide access to a family of related concrete components (the different possible instantiations of the class template): flyweight internally selects the particular component appropriate for its internal needs.

Custom factories

In a way, factories resemble unique associative containers like std::set, though their expected interface is much more concise:

// example of a possible factory class template

template<typename Entry,typename Key>
class custom_factory_class
{
public:
  typedef ... handle_type;
  
  handle_type  insert(const Entry& x);
  void         erase(handle_type h);
  const Entry& entry(handle_type h);
};

Factories are parameterized by Entry and Key: the first is the type of the objects stored, while the second is the public key type on which flyweight operates (e.g. the std::string in flyweight<std::string> or flyweight<key_value<std::string,texture> >). An entry holds a shared value to which flyweight objects are associated as well as internal bookkeeping information, but from the point of view of the factory, though, the only fact known about Entry is that it is implicitly convertible to const Key&, and it is based on their associated Key that entries are to be considered equivalent or not. The factory insert() member function locates a previously stored entry whose associated Key is equivalent to that of the Entry object being passed (for some equivalence relation on Key germane to the factory), or stores the new entry if no equivalent one is found. A handle_type to the equivalent or newly inserted entry is returned; this handle_type is a token for further access to an entry via erase() and entry(). Consult the reference for the formal definition of the Factory concept.

Let us see an actual example of realization of a custom factory class. Suppose we want to trace the different invocations by Boost.Flyweight of the insert() and erase() member functions: this can be done by using a custom factory whose member methods emit trace messages to the program console. We base the implementation of the repository functionality on a regular std::set:

template<typename Entry,typename Key>
class verbose_factory_class
{ 
  typedef std::set<Entry,std::less<Key> > store_type;

  store_type store;

public:
  typedef typename store_type::iterator handle_type;

  handle_type insert(const Entry& x)
  {
    std::pair<handle_type, bool> p=store.insert(x);
    if(p.second){ /* new entry */
      std::cout<<"new: "<<(const Key&)x<<std::endl;
    }
    else{         /* existing entry */
      std::cout<<"hit: "<<(const Key&)x<<std::endl;
    }
    return p.first;
  }

  void erase(handle_type h)
  {
    std::cout<<"del: "<<(const Key&)*h<<std::endl;
    store.erase(h);
  }

  const Entry& entry(handle_type h)
  {
    return *h;
  }
};

The code deserves some commentaries:

In order to plug a custom factory into the specification of a flyweight type, we need an associated construct called the factory specifier. A factory specifier is a Lambda Expression accepting the two argument types Entry and Key and returning the corresponding factory class:

// Factory specifier (metafunction class version)

struct custom_factory_specifier
{
  template<typename Entry,Key>
  struct apply
  {
    typedef custom_factory_class<Entry,Key> type;
  } 
};

// Factory specifier (placeholder version)

typedef custom_factory_class<
  boost::mpl::_1,
  boost::mpl::_2
> custom_factory_specifier;

There is one last detail: in order to implement flyweight free-order template parameter interface, it is necessary to explicitly tag a factory specifier as such, so that it can be distinguised from other types of specifiers. Boost.Flyweight provides three different mechanisms to do this tagging:

  1. Have the specifier derive from the dummy type factory_marker. Note that this mechanism cannot be used with placeholder expressions.
    #include <boost/flyweight/factory_tag.hpp>
    
    struct custom_factory_specifier: factory_marker
    {
      template<typename Entry,Key>
      struct apply
      {
        typedef custom_factory_class<Entry,Key> type;
      } 
    };
    
  2. Specialize a special class template called is_factory:
    #include <boost/flyweight/factory_tag.hpp>
    
    struct custom_factory_specifier{};
    
    namespace boost{
    namespace flyweights{
    
    template<> struct is_factory<custom_factory_specifier>: boost::mpl::true_{};
    
    }
    }
    
  3. The third mechanism, which is the least intrusive, consists in wrapping the specifier inside the factory construct:
    #include <boost/flyweight/factory_tag.hpp>
    
    typedef flyweight<
      std::string,
      factory<custom_factory_specifier>
    > flyweight_string;
    

Example 7 in the examples section develops in full the verbose_factory_class case sketched above.

Custom holders

A holder is a class with a static member function get() giving access to a unique instance of a given type C:

// example of a possible holder class template

template<typename C>
class custom_holder_class
{
public:
  static C& get();
};

flyweight internally uses a holder to create its associated factory as well as some other global data. A holder specifier is a Lambda Expression accepting the type C upon which the associated holder class operates:

// Holder specifier (metafunction class version)

struct custom_holder_specifier
{
  template<typename C>
  struct apply
  {
    typedef custom_holder_class<C> type;
  } 
};

// Holder specifier (placeholder version)

typedef custom_holder_class<boost::mpl::_1> custom_factory_specifier;

As is the case with factory specifiers, holder specifiers must be tagged in order to be properly recognized when provided to flyweight, and there are three available mechanisms to do so:

// Alternatives for tagging a holder specifier

#include <boost/flyweight/holder_tag.hpp>

// 1: Have the specifier derive from holder_marker

struct custom_holder_specifier: holder_marker
{
  ...
};

// 2: Specialize the is_holder class template

namespace boost{
namespace flyweights{

template<> struct is_holder<custom_holder_specifier>: boost::mpl::true_{};

}}

// 3: use the holder<> wrapper when passing the specifier
// to flyweight

typedef flyweight<
  std::string,
  holder<custom_holder_specifier>
> flyweight_string;

Custom locking policies

A custom locking policy presents the following simple interface:

// example of a custom policy

class custom_locking
{
  typedef ... mutex_type;
  typedef ... lock_type;
};

where lock_type is used to acquire/release mutexes according to the scoped lock idiom:

mutex_type m;
...
{
  lock_type lk(m); // acquire the mutex
  // zone of mutual exclusion, no other thread can acquire the mutex
  ...
} // m released at lk destruction

Formal definitions for the concepts Mutex and Scoped Lock are given at the reference. To pass a locking policy as a template argument of flyweight, the class must be appropriately tagged:

// Alternatives for tagging a locking policy

#include <boost/flyweight/locking_tag.hpp>

// 1: Have the policy derive from locking_marker

struct custom_locking: locking_marker
{
  ...
};

// 2: Specialize the is_locking class template

namespace boost{
namespace flyweights{

template<> struct is_locking<custom_locking>: boost::mpl::true_{};

}}

// 3: use the locking<> wrapper when passing the policy
// to flyweight

typedef flyweight<
  std::string,
  locking<custom_locking>
> flyweight_string;

Note that a locking policy is its own specifier, i.e. there is no additional class to be passed as a proxy for the real component as is the case with factories and holders.

Custom tracking policies

Tracking policies contribute some type information to the process of definition of the internal flyweight factory, and are given access to that factory to allow for the implementation of the tracking code. A tracking policy Tracking is defined as a class with the following nested elements:

Each of these elements build on the preceding one, in the sense that Boost.Flyweight internal machinery funnels the results produced by an element into the following: So, in order to define the factory of some instantiation fw_t of flyweight, Tracking::entry_type is invoked with an internal type Value implicitly convertible to const fw_t::key_type& to obtain the entry type for the factory, which must be convertible to both const Value& and const fw_t::key_type&. Then, Tracking::handle_type is fed an internal handle type and a tracking policy helper to produce the factory handle type. The observant reader might have detected an apparent circularity: Tracking::handle_type produces the handle type of the flyweight factory, and at the same time is passed a tracking helper that grants access to the factory being defined! The solution to this riddle comes from the realization of the fact that TrackingHandler is an incomplete type by the time it is passed to Tracking::handle_type: only when Handle is instantiated at a later stage will this type be complete.

In order for a tracking policy to be passed to flyweight, it must be tagged much in the same way as the rest of specifiers.

// Alternatives for tagging a tracking policy

#include <boost/flyweight/tracking_tag.hpp>

// 1: Have the policy derive from tracking_marker

struct custom_tracking: tracking_marker
{
  ...
};

// 2: Specialize the is_tracking class template

namespace boost{
namespace flyweights{

template<> struct is_tracking<custom_tracking>: boost::mpl::true_{};

}}

// 3: use the tracking<> wrapper when passing the policy
// to flyweight

typedef flyweight<
  std::string,
  tracking<custom_tracking>
> flyweight_string;

Tracking policies are their own specifiers, that is, they are provided directly as template arguments to the flyweight class template.




Revised December 2nd 2008

© Copyright 2006-2008 Joaquín M López Muñoz. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)