Boost C++ Libraries

PrevUpHomeNext

Abstract Factory

Tutorial
Examples
Reference
Performance

An abstract factory is signified by having multiple, abstract base types whose implementations can be specified at runtime and accessed through types (unlike the "conventional" factory pattern which has a single abstract base type and accessed through identifiers). An example used in Modern C++ Design is that you're programming a game and you have several base classes for your enemies (abstract_soldier, abstract_monster etc), and that you want to select their implementations based on the difficulty the user has chosen (easy_soldier vs. hard_soldier etc). This could be done with the classical "switch-case" pseudopattern, but as one might imagine, doing that is rarely ideal for any situation where extendability/flexibility is desired. Multiple conventional factories could also have been used, one for each base class, but this could quickly become unvieldy to manage, and as we will soon see, using an abstract factory gives us the flexibility without the hassle.

Header

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

Like with the single base class factory, all abstract factory functionality resides in the boost::factory namespace.

With an abstract factory approach we're dealing with the abstract factory itself, operating on (you guessed it) abstract types.

Declaring an abstract factory needs only a simple typedef:

typedef boost::factory::abstract_factory<type0,...,typen> my_factory_t;

Building upon the game development example mentioned in the introduction, let us consider the case where we have three abstract classes we want to be able to instantiate at runtime. For the simplest case, we just specify the types that we want to factory to return. In this case, we just want plain old pointers to the newly created objects (note that the result types must be pointers of some sort, be it smart pointers or plain pointers):

using namespace boost::factory;

typedef abstract_factory<   
  abstract_soldier*, abstract_monster*, abstract_beast*
> enemy_factory_t;

Since an abstract factory is—by its very definition—abstract, we need concrete factories to do the actual dirty work. They take a template argument for the abstract factory type they provide an implementation of, as well as a list of concrete "creator" types in the same order as the types in the abstract factory. If a given type does not have a special meaning to the factory (i.e. it's a regular class), the creator is implicitly given as operator new.

typedef concrete_factory<
    enemy_factory_t
  , easy_soldier, easy_monster, gentle_bunny
> easy_enemy_factory_t;

easy_enemy_factory_t easy_factory;

...

typedef concrete_factory<  
    enemy_factory_t
  , hard_soldier, hard_monster, ferocious_bunny
> hard_enemy_factory_t;

hard_enemy_factory_t hard_factory;

Note that the concrete factories did not specify that the objects be returned as pointers. This is all handled by the abstract factory, and lets us change the returned type to eg. a std::auto_ptr without changing any of the concrete factory types (we'll get to this shortly).

[Important] Important

The implementation types must match the position of their corresponding abstract types in the declaration!

To make metaprogramming around these types easier, the first template parameter (i.e. the first abstract type) for an abstract factory, as well as the second template parameter for a concrete factory (i.e. the first concrete type) may be an MPL Sequence. In this case, any subsequent parameters will be ignored and the elements of the sequence used instead:

typedef abstract_factory<   
  mpl::vector<abstract_soldier*, abstract_monster*, abstract_beast*>
> enemy_factory_t;

...

typedef concrete_factory<  
    enemy_factory_t
  , mpl::vector<hard_soldier, hard_monster, ferocious_bunny>
> hard_enemy_factory_t;
Undecorated base types and what they imply

Throughout this documentation, the term "undecorated" will be used whenever an abstract base type should be specified without any accompanying function type or pointer declaration etc.

That means

abstract_monster*(const std::string&, int)

or

std::auto_ptr<abstract_monster>(const std::string&, int)

should be reduced to only

abstract_monster

This undecorated form is used by most (if not all) of the factory's member functions.

FIXME: this is might be a somewhat unintuitive (or even wrongful) term

Creating objects

The abstract factory exposes the

create<undecorated-abstract-type>()

member function, which will invoke the proper creator for that type and return a pointer to the newly created object. Exactly what the "proper creator" is, depends on the concrete factory type that the abstract factory is an instance of.

Example:

void spawn_monster(const enemy_factory_t& fac)
{
  std::auto_ptr<abstract_monster> monster( fac.create<abstract_monster>() );
}

If fac is an instance of easy_enemy_factory_t, an easy_soldier object will be returned and so on.

Depending on the underlying policy that governs the object creation for a given concrete type, the function may or may not throw an exception if certain preconditions are unfulfilled. See later sections for more details.

Passing as independent factory fields

It's not always feasible to know the full type of the abstract factory, so by design an abstract factory can be passed as individual fields for its abstract types.

To see why this is possible, here is an illustration of the overall hierarchy created by the example we saw earlier:

abstract_factory_hierarchy

Each abstract_factory_field acts as a virtual base class, which means they all add a vtable to the mix and can thus be upcasted to properly when passing. The operator new creators merely override the abstract fields' vtables and contain no state, so they will not add any more space overhead.

The following code shows how the decoupling allows for code that is functionally equivalent between knowing the full type of the abstract factory and only knowing a single abstract type:

void create_soldier_from_factory(const enemy_factory_t& f)
{
  std::auto_ptr<abstract_soldier> s( f.create<abstract_soldier>() );
}

void create_soldier_from_field(const abstract_factory_field<abstract_soldier*>& field)
{
  std::auto_ptr<abstract_soldier> s( field.create() );
}

void do_stuff()
{
  easy_enemy_factory_t f;

  // f can be directly passed to both functions
  create_soldier_from_factory(f);
  create_soldier_from_field(f);
}

When an abstract type field is passed, there's no ambiguity about the type that should be created (as the field knows of only its own type), so its creation function does not take any template args.

Often you will want to be able to provide arguments to the object constructors. For the game enemy example, perhaps you want to set a unique name string and hitpoints for each monster. Doing this with Boost.Factory is very simple: all you have to do is to declare the abstract type as a function type taking the desired set of parameters:

typedef abstract_factory<
  abstract_monster*(const std::string&, int)
> myfactory_t;

The concrete factory type does not need to be changed—the construction-arguments to its concrete types are deduced from its corresponding abstract factory's types.

Creating an object now naturally needs arguments, so we provide these directly to the create function. Note that the full abstract type function type needs not (and in fact, cannot) be supplied as the template argument to create. Only use the "undecorated" form of the abstract type.

std::auto_ptr<abstract_monster> new_monster(
  my_factory.create<abstract_monster>("Smelly troll", 120)
);

In the case of a factory being passed as a field, the full function type must be given, as the arguments cannot be deduced otherwise.

void do_stuff(const abstract_factory_field<abstract_monster*(const std::string&, int)>& field)
{
  std::auto_ptr<abstract_monster> new_monster(
    field.create("Smelly troll", 120)
  );
}

Non-const rvalues (i.e. unnamed temporaries) may be used due to the way arguments are forwarded internally.

[Important] Important

The argument forwarding wraps everything as references internally, but at the point when create is called they are forwarded as the exact type that is given in the function type signature (to easily allow rvalues while at the same time enforcing pass-by-reference). That is to say, const std::vector<int>& arguments are forwarded by const reference, but a std::vector<int> argument is forwarded by value. As such, it is important that you specify the function signature correctly in order to avoid unecessary copying.

A future version will be C++1x compatible and use rvalue references and perfect forwarding to make this far less of an issue.

The maximum number of arguments is currently 6. This number may be changed by setting the BOOST_ABSTRACT_FACTORY_CREATE_MAX_ARITY definition before any abstract factory headers are included. However, there are several dependencies on other libraries, such as Fusion, which will not have their definitions automatically changed, meaning that these have to be altered as well in order to successfully compile (TODO: identify and document all these).

As you may already have deduced, you're not limited to getting (potentially exception-unsafe) plain pointers in return from the factory. Since the types in the abstract factory specify what's actually returned (normally a plain pointer), changing to a smart pointer is as easy as changing this type to indicate the desired smart pointer type:

typedef abstract_factory<   
    std::auto_ptr<abstract_soldier>
  , boost::shared_ptr<abstract_monster>(const std::string&, int)
  , std::tr1::shared_ptr<abstract_beast>
> enemy_factory_with_smart_ptrs_t;

Concrete factory types remain the same, as they don't concern themselves with how (or if) the returned objects are wrapped:

typedef concrete_factory<  
    enemy_factory_with_smart_ptrs_t
  , hard_soldier, hard_monster, ferocious_bunny
> hard_enemy_factory_t;

The same goes for calls to create (i.e. it still takes the same, undecorated type as it did before). When passing as factory fields, however, the full type must be given as usual (as otherwise the field wouldn't have the faintest clue of how it should return the object):

void do_stuff(const abstract_factory_field<
    boost::shared_ptr<abstract_monster>(const std::string&, int)
  >& field)
{
  field.create("Smelly troll", 120);
}

Non-plain pointer returns are automatically supported if they contain a nested element_type typedef, which is the case with the standard smart pointer types.

Another way of handling the creation of concrete objects is to use prototype objects that are cloned, rather than constructing new objects "from scratch". Boost.Factory allows you to decide on a per-type basis whether or not you want to use a prototype object, and this is done by specifying prototype<policy-options> as the concrete type and then assigning the prototype instance itself. Using the default policies, all prototypeable abstract classes must have a (pure) virtual clone() function taking no arguments and returning a pointer to a newly created clone.

Header

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

Arguments passed to create are completely ignored for prototypes, so prototype objects need not actually meet the constructor requirements that regular concrete types do:

class drink
{
public:
  virtual ~drink() {}
  
  virtual drink* clone() const = 0;
};  

...

class tea : public drink
{
  std::string type_;
  int temperature_;
public:
  // Non-matching constructor
  tea(const std::string& type, int temperature)
    : type_(type), temperature_(temperature)
  {}
  
  tea(const tea& other)
    : type_(other.type_), temperature_(other.temperature_)
  {}
    
  virtual drink* clone() const { return new tea(*this); }
};

...

using namespace boost::factory;

// The abstract factory type itself
typedef abstract_factory<
    std::auto_ptr<drink>(const std::string&, const amount&)
  , std::auto_ptr<toast>(const spread&)
> abstract_breakfast_factory_t;

void make_breakfast(const abstract_breakfast_factory_t& f)
{
  // The arguments to create() will be ignored
  std::auto_ptr<drink> cuppa( f.create<drink>("fresh", amount_in_liters(0.25)) );
  ...
}

...

// Specify that a prototype with default policies should be used for drink
typedef concrete_factory<
    abstract_breakfast_factory_t
  , prototype<>, tasty_toast
> decent_breakfast_factory_t;

decent_breakfast_factory_t f;

// Assign the actual prototype
f.prototype<drink>(new tea("green", 100));

make_breakfast(f);

When you assign prototype<> to be the concrete type, getter/setter member-functions are made available. These are only accessible through the concrete factory itself, not references to the abstract factory's type (as the abstract factory has no notion of prototypes).

If all concrete types are to be prototypes, a wrapper that simply sets all concrete types to prototype<> is available:

typedef prototype_factory<abstract_breakfast_factory_t> prototype_breakfast_factory_t;
Setting prototypes

Prototypes are set with the prototype<undecorated-abstract-type>(ptr) member-function, which takes a single pointer argument for the prototype object.

f.prototype<drink>(new tea("green", 100));

There are overloads for raw pointers, std::auto_ptr and boost::shared_ptr. Prototypes are stored in boost::shared_ptrs internally.

[Caution] Caution

When setting a prototype using a raw pointer, the factory will take ownership of the pointer, that is, it will be deleted when the concrete factory goes out of scope (this is also the case with std::auto_ptr, but that's implied by its semantics). If this is not desired, use a boost::shared_ptr instead

Getting prototypes

Retrieving a stored prototype is done similarly, through prototype<undecorated-abstract-type>(), which returns a boost::shared_ptr to the stored object. If no prototype has been stored, the pointer will be NULL.

boost::shared_ptr<drink> d = f.prototype<drink>();
Cloning and exception policies

The full type definition of prototype<> has policies for specifying how objects are cloned, as well as what happens when create is called on a prototype that hasn't been set:

template <
    class ClonePolicy = invoke_clone_function
  , class ErrorPolicy   = exception_on_missing_prototype
>
struct prototype
{
  typedef ClonePolicy creator_policy_type;
  typedef ErrorPolicy error_policy_type;
};

These policies may also be set on prototype_factory, in which case they will apply to all prototypes within it:

template <
    class AbstractFactory
  , class ClonePolicy = invoke_clone_function
  , class ErrorPolicy   = exception_on_missing_prototype
>
class prototype_factory
  ...

The default policies should hopefully be self-explanatory.

There's currently only one additional error policy available for prototype<>, that being nullptr_on_missing_prototype, which instead of throwing an exception returns NULL. This should only be used if sufficient null-checks are performed by the caller.

Using a function type with arguments in the abstract factory declaration specifies the input arguments that should always be provided to create. It does not however specify that all (or any) of these arguments must ever reach their target constructor, if there is only a subset of those arguments the concrete type is actually interested in. People familiar with Boost.Bind already know how the signature of a target function can be vastly different from the one actually invoked through argument binding. A similar functionality is provided by Boost.Factory, and allows you to bind arbitrary input arguments to arbitrary constructor arguments.

Header

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

To revisit the above breakfast example, let us assume that the economic recession has forced us to drink only water and eat plain toast with nothing on it for breakfast:

class water : public drink
{
public:
  water(const amount&);
};

class plain_toast : public toast
{
public:
  toast(); // Default constructor only
};

We can now create a concrete factory that accounts for this, but lets us leave the abstract factory and all calls to create completely unchanged:

using namespace boost::factory::placeholders; // Can also use boost::factory::_1 etc

typedef concrete_factory<
    abstract_breakfast_factory_t
  , bind<water(_2)>, bind<plain_toast()>
> cheap_breakfast_factory_t;

When passing an instance of cheap_breakfast_factory_t to make_breakfast, doing

f.create<drink>("fresh", amount_in_liters(0.25))

will invoke the water constructor as if the following code were written:

new water(amount_in_liters(0.25));

The first argument is not bound, and as such will be discarded. Similarly,

f.create<toast>(spread("jam"))

will create a plain_toast object with no arguments, since none are bound to it. Since we're dealing with concrete types here, the function type does not have a pointer return type (i.e. water(_2) is correct, water*(_2) is not).

The maximum _n is the arity of a abstract factory's given function type, so for abstract_foo*(int, double, char), _1, _2 and _3 may be used. These can be used in any order and with any frequency, as long as there is a constructor in the concrete type accepting such an arrangement. Since normal function resolution rules apply, you can have a class with many constructors and bind<> will select the proper one to use.

Assuming the above abstract_foo is used and that all foo_impl types extend this type, the following are all examples of valid bindings (constructor declarations shown on the first line of each example):

Binding to none of the input arguments:

foo_impl1();
...
bind<foo_impl1()>

Binding to only some arguments:

foo_impl2(double, char);
...
bind<foo_impl2(_2, _3)>

Binding input arguments to multiple constructor arguments:

foo_impl3(int, int, int);
...
bind<foo_impl3(_1, _1, _1)>

Identity-binding arguments (same end-result as not using bind<> at all):

foo_impl4(int, double, char);
...
bind<foo_impl4(_1, _2, _3)>

Arbitrary arrangement binding:

foo_impl5(char, int, int, double, char, double);
...
bind<foo_impl5(_3, _1, _1, _2, _3, _2)>

And so on.

Note that you currently do not have the flexibility to provide other arguments than those available from create to the constructors. This is a limitation when compared to Boost.Bind et al, and might be changed in the future (although there are certain inescapable limitations considering these are compile-time types only). If this is a concern, see the section on Dynamic Functions.

[Caution] Caution

Including Boost.Bind together with constructor argument binding will cause the compiler to give an ambiguity-error if you specify using namespace boost::factory or using namespace boost::factory::placeholders, as Boost.Bind brings _1, _2 etc. into the global namespace. The solution here is to either be explicit about the type in the template arguments, or ensure the factory placeholders are used through using boost::factory::_1, using boost::factory::_2 etc

If none of the aforementioned concrete creator approaches give you sufficient flexibility in how objects are created, there's the dynamic function creator, which leaves the policy on how to create objects entirely up to you. It stores a Boost.Function internally, with a function type signature matching that of the abstract type. This lets you use any function, functor, lambda statement et al with the factory (just as with the conventional factory).

[Note] Note

Dynamic functions must currently always return raw pointers. This is because all internal dispatch functions also use raw pointers, only performing the std::auto_ptr/ boost::shared_ptr "wrapping" at the very end before the newly created object is returned to the caller.

Header

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

Specifying the creator as a dynamic function is done by setting the concrete type to dynamic<>, and is assigned and fetched in the same way as with prototypes.

typedef abstract_factory<
    std::auto_ptr<drink>(const std::string&, const amount&)
  , std::auto_ptr<toast>(const spread&)
> abstract_breakfast_factory_t;

typedef concrete_factory<
    abstract_breakfast_factory_t
  , dynamic<>, dynamic<>
> dynamic_breakfast_factory_t;

class coffee : public drink { ... };

// Note: raw pointer return value!
drink* make_coffee(const std::string& kind, const amount& amt)
{
  return new coffee(kind, amt);
}

...

dynamic_breakfast_factory_t dbf;

// All drinks should be black coffee regardless of input to create()
// The amount should be forwarded
dbf.dynamic<drink>(boost::bind(make_coffee, "black", _2));

// Creates 0.15l of black coffee when invoked
std::auto_ptr<drink> d( dbf.create<drink>("fresh", amount_in_liters(0.15)) );

// Using the default error policy, this would throw an exception, as no
// function has been assigned to it:
// std::auto_ptr<toast> t( dbf.create<toast>(strawberry_jam()) );

The full definition of dynamic<> is as follows:

template <
  class ErrorPolicy = exception_on_missing_dynamic
>
struct dynamic
{
  typedef ErrorPolicy error_policy_type;
};

As with prototypes, a wrapper type that sets all concrete types to dynamic is available:

template <
    class AbstractFactory
  , class ErrorPolicy = exception_on_missing_dynamic
>
class dynamic_factory
  ...

typedef dynamic_factory<abstract_breakfast_factory_t> dynamic_breakfast_factory_t;

TODO

TODO: `prototype<>`, `bind<>`, `dynamic<>`

Location

Name

Value

File

factory/abstract_factory.hpp

Namespace

boost::factory

Declaration
template <typename T1, ..., typename Tn>
class abstract_factory

where n is given as BOOST_ABSTRACT_FACTORY_MAX_PARAMS. Overriding this can be done by setting a different value of the #define before including abstract_factory.hpp. By default it is given as:

#ifndef BOOST_ABSTRACT_FACTORY_MAX_PARAMS
#  define BOOST_ABSTRACT_FACTORY_MAX_PARAMS 10
#endif

Each template parameter must be in the form

pointer-to-abstract-type

or, if arguments are required

pointer-to-abstract-type(A1, ..., An)

where pointer-to-abstract-type is either a raw pointer such as foo* or any smart pointer with a nested element_type type. The latter holds true smart pointers such as std::auto_ptr, std::tr1::shared_ptr, boost::shared_ptr et al.

n is given as BOOST_ABSTRACT_FACTORY_CREATE_MAX_ARITY. Changing this definition is possible but not immediately trivial as it involves changing the arity definitions of other Boost libraries as well (FIXME: list). This is not done automatically by the library since it could conflict with existing Boost code used in your project.

T1 may be an MPL Sequence, in which case its elements are used rather than those of T2 ... Tn, which are then ignored.

Methods

There is only one method exposed by abstract_factory itself:

template <BaseAbstract>
pointer-to-abstract-type create(A1 arg_1, ..., An arg_n) const

The semantics of "abstract base types" are covered in the tutorial. Both the returned type and the number/type of the arguments correspond exactly to what is given in the abstract factory's declaration for a given abstract type.

The concrete factory class gives an actual implementation of a given abstract factory type. Any abstract factory may have an arbitrary amount of concrete factories subclassing it.

Location

Name

Value

File

factory/abstract_factory.hpp

Namespace

boost::factory

Declaration
template <
    typename AbstractFactory
  , typename T1, ..., typename Tn
>
class concrete_factory

where AbstractFactory is a valid type declaration of abstract_factory and n is given by BOOST_ABSTRACT_FACTORY_MAX_PARAMS.

T1 may be an MPL Sequence, in which case its elements are used rather than those of T2 ... Tn, which are then ignored. In either case, the number of elements must match the number of abstract types in AbstractFactory.

The types listed after abstract factory type (or given by the MPL Sequence) become the concrete "creators" for the types at their corresponding locations in the abstract factory. This means their relative ordering matter. If you simply specify a "regular" class for a concrete type (i.e. one that doesn't hold a specific meaning to the library), invoking the create method will create the given concrete type using operator new and a constructor matching the arguments specified in the abstract type. If no such arguments are specified, the default constructor is invoked.

Methods

concrete_factory does not explicitly contain any methods, but depending on the creators specified (and thus the class hierarchy generated), these may do so. One example is prototype<>, which introduces methods for storing and retrieving prototype objects.

These libraries in turn have other interdependencies, but these are not listed here due to being implementational details and subject to change (also a pain to track down).

One of the main goals of the abstract factory implementation is that extensive metaprogramming moves most of the overhead from run-time to compile-time, allowing an easy-to-use interface to be presented without sacrificing performance. Due to the actual object creation policies always being unknown to the abstract factory type, there's an inherent virtual function call cost associated with every invocation of create. When creating objects with no arguments, the actual cost most likely will be nearly identical to that of a handwritten virtual factory function. When arguments are used, there's a slight added forwarding-cost associated with first taking in the arguments from the callsite, then packing them up in reference wrappers in a Fusion sequence before invoking the target creator with said sequence. The important part is to never specify datatypes that are expensive to copy as being passed by value in the abstract type's function signature! Always use (const) references for this.

A future version may forego the current strategy of always using the exact types specified in favor of using Boost.CallTraits or something similar. When C++1x comes around, rvalue references and perfect forwarding removes this problem.


PrevUpHomeNext