![]() |
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 |
|---|---|
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;
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
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.
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:
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 |
|---|---|
|
The argument forwarding wraps everything as references internally, but
at the point when 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;
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 |
|---|---|
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 |
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>();
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 |
|---|---|
Including Boost.Bind
together with constructor argument binding will cause the compiler to
give an ambiguity-error if you specify |
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 |
|---|---|
Dynamic functions must currently always return raw pointers. This is
because all internal dispatch functions also use raw pointers, only performing
the |
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<>`
|
Name |
Value |
|---|---|
|
File |
|
|
Namespace |
|
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.
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.
|
Name |
Value |
|---|---|
|
File |
|
|
Namespace |
|
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.
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.