Boost C++ Libraries

PrevUpHomeNext

Factory

Tutorial
Examples
Reference

The single-base pattern considers the common scenario where you have some base (abstract) class whose subclasses are instantiated at runtime based on an identifier.

Example: you have an abstract base class animal and based on values read at runtime, instances of this class' concrete subclasses have to be created (eg. tiger for a "tiger" string, maneating_crocodile for a "evil crododile" string and so on).

Header

#include <include-path/factory/factory.hpp>

Object creation is based on two operations:

  1. Registration of a mapping from an identifier (such as a string) to a creator function. The creator function can be anything that can be fit into a Boost.Function object, such as functors, Boost.Bind expressions and so on (and of course regular functions). And if this isn't enough, you can also override the type used for storing creator functions internally. This will be covered shortly.
  2. Lookup and invocation. The factory maintains the creator function mappings, and when an identifier is looked up, the matching creator will be returned so that it may be invoked. Error-policies specify what happens if there's no matching mapping.

Factory-functionality is contained, as one might expect, in the factory class residing in the boost::factory namespace:

template<
    typename Signature
  , typename A1 = parameter::void_
  , typename A2 = parameter::void_
  , typename A3 = parameter::void_
>
class factory
  ...

This calls for an explanation! The first parameter is the signature (i.e. function type) associated with the "creator functions" you wish to use with the factory. A creator function is essentially a function (or function object) that an identifier maps to, and its responsibility is to perform the actual object creation (e.g. using operator new). For anyone familiar with Boost.Function, the use of function style template parameters should not be a new thing—it is given exactly like a regular function definition (note: not a function pointer definition!) and is just as flexible. E.g.; foo*(int, long), which says that all the functions registered with the factory must take an int, a long and return a pointer to foo.

The rest of the template parameters might understandably be harder to grasp. That is because the factory implementation uses Boost.Parameter in order to provide for tagged, order-agnostic parameters. Normally, if one needed to change the last parameter of a templated class with default parameter values, one would have to give values for all the parameters coming before it as well. With tagged parameters, as we will soon see, this is not necessary. Note that it is completely optional whether or not to use the tagged parameter support.

It's a busy day at the ACME Zoo, so let's get started with a simple example! The details will be explained afterwards.

class animal
{
  virtual const std::string do_sound() const = 0;
  std::string name_;
  int age_;
public:
  animal(const std::string& name, int age)
    : name_(name), age_(age)
  {}

  virtual ~animal() {}

  const std::string sound() const
  {
    return do_sound();
  }

  const std::string& name() const { return name_; }
  const int age() const { return age_; }
};

class tiger : public animal
{
  const std::string do_sound() const { return "roar!"; }
public:
  tiger(const std::string& name, int age)
    : animal(name, age)
  {}
};

class crocodile : public animal
{
  const std::string do_sound() const { return "blub blub"; } // Might not be scientifically accurate
public:
  crocodile(const std::string& name, int age)
    : animal(name, age)
  {}
};

using namespace boost::factory;

// We want to construct all animal subclass instances with a string and an int
typedef factory<
    animal*(const std::string&, int)
  , id_type<std::string>
  , map_type<std_map_adapter>
  , error_policy<exception_on_missing_id>
> myfactory_t;

...

myfactory_t f;

// Register a default (operator new) creator function
// for an implementation type with a raw pointer return value
register_new_ptr<tiger>(f, "tiger");
register_new_ptr<crocodile>(f, "croc");
  
// Create objects through the factory
std::auto_ptr<animal> a1( f["tiger"]("mr. snuggles", 5) );
std::auto_ptr<animal> a2( f["croc"]("sir bitesalot", 12) );

std::cout << a1->sound() << std::endl; // Outputs "roar!"
std::cout << a2->sound() << std::endl; // Outputs "blub blub"

The factory typedef given here is intentionally verbose, as it showcases the supported template parameters and how tagging is used.

A short explanation of the template parameters is in order:

Template parameters (tagged or untagged)

id_type

Type used for the value that identifies a given produceable type. Defaults to std::string if none is given.

map_type

An MPL Metafunction class that specifies the associative container implementation to use for the map. std_map_adapter, perhaps unsurprisingly, is for std::map, and is the default if none other is specified. There is also an unordered_map_adapter included in unordered_map_adapter.hpp if your compiler has TR1 library support (true for Visual C++ 2008 and up and newer versions of GCC) or if you're using Boost 1.36+. If an unordered map is used, there must exist a valid hasher for id_type.

error_policy

A type that determines what happens if the factory is invoked with an identifier that has not been mapped. Defaults to exception_on_missing_id, which throws a std::invalid_argument exception.

Since all the above parameters are optional, both existence-wise and position-wise (when tagged), the following typedefs are functionally equal:

No tagging (requires parameters to be in the following, correct order):

typedef factory<
    animal*(const std::string&, int)
  , std_map_adapter
  , factory_exception_policy
  , std::string
> myfactory_t;

Mixed parameter order:

typedef factory<
    animal*(const std::string&, int)
  , map_type<std_map_adapter>
  , error_policy<exception_on_missing_id>
  , id_type<std::string>
> myfactory_t;

All defaults:

typedef factory<animal*(const std::string&, int)> myfactory_t;

As mentioned earlier, you don't have to give values to parameters you don't care about, so say that you only wanted to change the error-policy, but leave everything else unchanged:

typedef factory<
    animal*(const std::string&, int)
  , error_policy<my_fancy_error_policy>
> myfactory_t;
Mapping creator functions

The function used for registering new creators is factory::register_creator(id, function). Some simple examples:

#include <boost/bind.hpp>
#include <include-path/factory/factory.hpp>

void do_stuff1()
{
  using boost::factory::factory;
  using boost::factory::new_ptr;

  factory<animal*(const std::string&, int)> f;

  f.register_creator("tiger", new_ptr<tiger>());
  f.register_creator("croc", new_ptr<crocodile>());

  // We can use Boost.Bind functors and placeholders as usual
  f.register_creator("unknown_tiger", boost::bind(new_ptr<tiger>(), "Mystery Tiger", _2));

  std::auto_ptr<animal> tiger1( f["unknown_tiger"]("", 12) );
  std::cout << tiger1->name() << " says " << tiger1->sound() << std::endl; // Outputs "Mystery Tiger says roar!"
}

Since registering a default creator is such a common operation, the utility-function register_new_ptr is available, as shown in the initial example.

using boost::factory::register_new_ptr;

register_new_ptr<tiger>(f, "tiger");

It's often desirable to have the factory return a smart/scoped pointer in order to prevent memory leaks in the face of exceptions etc. This can be done simply by specifying the smart pointer type as the result type of the factory function signature, and using creator functions with that type as a result type. As a convenience, there is a wrapped_new_ptr (and corresponding register_wrapped_new_ptr) class available for such purposes, which should work with pretty much any smart pointer type.

void do_stuff2()
{
  using boost::factory::factory;
  using boost::factory::wrapped_new_ptr;
  using boost::factory::register_wrapped_new_ptr;

  factory<std::auto_ptr<animal>(const std::string&, int)> f;

  // Need to explicitly specify the wrapped type when using wrapped_new_ptr
  f.register_creator("tiger", wrapped_new_ptr<tiger, std::auto_ptr<animal> >());
  
  // register_wrapped_new_ptr auto-deduces the wrapped type from the result type
  register_wrapped_new_ptr<crocodile>(f, "croc");

  // We can use Boost.Bind functors and placeholders as usual
  f.register_creator("unknown_tiger", boost::bind(
      wrapped_new_ptr<tiger, std::auto_ptr<animal> >(), "Mystery Tiger", _2));

  std::auto_ptr<animal> tiger1( f["unknown_tiger"]("", 12) );
  std::cout << tiger1->name() << " says " << tiger1->sound() << std::endl; // Outputs "Mystery Tiger says roar!"
}
Invoking creator functions

Creator functions are accessed via operator [], which takes the identifier as an argument and returns a const reference to the stored function that it maps to. If no such mapping exists, the error policy is invoked. This throws an exception by default, but it is easy to create your own error policy to eg. return a function that creates a default object or something similar. As operator [] returns a reference to the function object, it can immediately be invoked through operator ():

f["tiger"]("Ol' Chomper", 20);
Overriding the function storage type

By default, the factory uses Boost.Function to store functions, but this can be overridden. Iff the first template argument to the factory is a function type (as is normally the case), Boost.Function will be used. However, if this is not the case, it is assumed that the type provided is valid for function storage and invocation (also requires a standard result_type nested typedef).

This can be handy if you need to use eg. std::tr1::function instead of boost::function as the default:

factory< std::tr1::function<animal*(const std::string&, int)> > f;

A factory needs not always actually create objects. It can have other application areas as well.

TODO: command-example etc

Table 1.1. Nested factory typedefs

Typedef

Description

functor_type

Type used for function storage. Defaults to boost::function<function-signature-type>

result_type

typename functor_type::result_type

id_type

The type given in id_type<> in the template parameter list, or std::string by default.

error_policy_type

The type given in error_policy<> in the template parameter list, or exception_on_missing_id by default.

functor_param_type

Boost.CallTraits parameter type for functor_type.

id_param_type

Boost.CallTraits parameter type for id_type.

map_type

Internal map type, as given by mpl::apply<MapAdapter, id_type, functor_type>::type, where MapAdapter is the type provided by map_type<type> during type definition.


Table 1.2. Factory methods (see above table for type-reference)

Method

Description

bool register_creator(id_param_type id, functor_param_type func)

Registers the mapping id -> function. Returns true if the mapping was successfully inserted, false if a mapping already exists for the given identifier.

const functor_type& operator[](const id_type& id) const

Returns a reference to the stored function that id maps to, or invokes the error policy if no such mapping exists.

bool unregister(id_param_type id)

Removes the mapping that id represents. Returns true if the mapping was actually removed, false otherwise (eg. if there was no such mapping in the first place)

bool registered(id_param_type id) const

Returns true if there exists a mapping from id to a function, false otherwise.

const map_type& mappings() const

Returns a const reference to the internal storage map. It is const as this function is not meant for modifications of the internal map (this is implementation-specific behaviour), but rather traversal etc. of it.



PrevUpHomeNext