...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
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:
flyweight
instantiation.
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.
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:
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
.
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 Entry
s
used as the internal repository.
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:
// 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:
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; } };
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_{}; } }
factory
construct:
#include <boost/flyweight/factory_tag.hpp> typedef flyweight< std::string, factory<custom_factory_specifier> > flyweight_string;
Example 8 in the examples section develops
in full the verbose_factory_class
case sketched above.
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;
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.
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:
Tracking::entry_type
.Tracking::handle_type
.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.
Lambda
Expression
Tracking::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.
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 September 1st 2014
© Copyright 2006-2014 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)