Boost.Flyweight Documentation - Tutorial - Extending Boost.Flyweight

# Boost.Flyweight Tutorial: Extending Boost.Flyweight

Configuring Boost.Flyweight
Boost.Flyweight tutorial
Technical issues

## Contents

- Introduction
- Custom factories
- Custom holders
- Custom locking policies
- Custom tracking policies

## 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:

- 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.

## 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(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(Entry&& x)
>       {
>         std::pair<handle_type, bool> p=store.insert(std::move(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:

- 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 `Entry`s 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:

>     // 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 8 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:

- 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 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.

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.

Configuring Boost.Flyweight
Boost.Flyweight tutorial
Technical issues

Revised September 27th 2024

© Copyright 2006-2024 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)