Boost.Flyweight Tutorial: Extending Boost.Flyweight

Contents

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:

The component itself (for instance, a factory class template).

The associated component specifier, which is the type
provided as a template argument of a flyweight
instantiation.

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.

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:

Note that the factory is parameterized by Entry
and Key, as these types are provided internally by Boost.Flyweight
when the factory is instantiated as part of the machinery of flyeight;
but there is nothing to prevent us from having more template parameters for
finer configuration of the factory type: for instance, we could extend
verbose_factory_class to accept some comparison predicate rather than
the default std::less<Key>, or to specify the allocator
used by the internal std::set.

The fact that Entry is convertible to const Key&
(which is about the only property known about Entry) is
exploited in the specification of std::less<Key> as
the comparison predicate for the std::set of Entrys
used as the internal repository.

As our public handle_type we are simply using an iterator to the
internal std::set.

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:

There is one last detail: in order to implement flyweightfree-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:

Have the specifier derive from the dummy type factory_marker.
Note that this mechanism cannot be used with placeholder expressions.

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:

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_markerstructcustom_holder_specifier:holder_marker{...};// 2: Specialize the is_holder class templatenamespaceboost{namespaceflyweights{template<>structis_holder<custom_holder_specifier>:boost::mpl::true_{};}}// 3: use the holder<> wrapper when passing the specifier
// to flyweighttypedefflyweight<std::string,holder<custom_holder_specifier>>flyweight_string;

// example of a custom policyclasscustom_locking{typedef...mutex_type;typedef...lock_type;};

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

mutex_typem;...{lock_typelk(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_markerstructcustom_locking:locking_marker{...};// 2: Specialize the is_locking class templatenamespaceboost{namespaceflyweights{template<>structis_locking<custom_locking>:boost::mpl::true_{};}}// 3: use the locking<> wrapper when passing the policy
// to flyweighttypedefflyweight<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.

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:

A type Tracking::entry_type.

A type Tracking::handle_type.

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:

Tracking::entry_type is a
Lambda
Expression accepting two different types named
Value and Key such that
Value is implicitly convertible to
const Key&. The expression is expected
to return
a type implicitly convertible to both const Value&
and const Key&.
Tracking::entry_type corresponds to the actual
type of the entries stored into the
flyweight factory:
by allowing the tracking policy to take part on the definition
of this type it is possible for the policy to add internal
tracking information to the entry data in case this is needed.
If no additional information is required,
the tracking policy can simply return Value as its
Tracking::entry_type type.

The binary Lambda
ExpressionTracking::handle_type is invoked
with types InternalHandle and TrackingHandler
to produce a type Handle, which will be used as the handle
type of the flyweight factory.
TrackingHandler
is passed as a template argument to Tracking::handle_type
to offer functionality supporting the implementation of the tracking
code.

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_markerstructcustom_tracking:tracking_marker{...};// 2: Specialize the is_tracking class templatenamespaceboost{namespaceflyweights{template<>structis_tracking<custom_tracking>:boost::mpl::true_{};}}// 3: use the tracking<> wrapper when passing the policy
// to flyweighttypedefflyweight<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.