A Boost-Centric Factory-Pattern Implementation

October 6, 2008 by Tor Brede Vekterli

Boost LogoThose of you who have read Andrei Alexandrescu's excellent Modern C++ Design-book should be well familiar with his Loki-library and its Factory/AbstractFactory implementations. Through clever metaprogramming and extensive use of compile-time policies, implementational details are kept hidden and coupling is greatly reduced (partially through auto-generation of class-hierarchies)—which in turn means better and more maintainable code can be effortlessly written by the library's users.

One of the fundamental concepts of Loki is the notion and use of typelists—that is, lists of types (rather than runtime values) that can be processed at compile-time using metaprogramming—a concept that has been greatly enhanced and extended with the Boost Metaprogramming Library (MPL). Since Loki and Boost exist independently, I figured it might be fun to adapt Alexandrescu's factory patterns into something that exploits the functionality already present in Boost, particularly the MPL and Boost.Preprocessor.

Last update: 7th of December, 2009. Updated post for version 1.2.2

> Factory patterns download and documentation <

The following offers only a short introduction, please refer to the documentation for details

Standard Factory Implementation

The first pattern considers the common scenario where you have some base (abstract) class whose subclasses are instantiated at runtime based on an identifier (e.g. you want to construct a kittylitter object for a "cat" identifier string but a fire_hydrant object for a "dog" string). This is implemented with a generic factory class:

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 associated with the "creator functions" you wish to use with the factory. A creator function is the name given to a 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 function objects registered with the factory must take an int, a long and return a pointer to foo.

The rest of the template parameters are understandably 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.

Let's just start with a simple example (note that all classes and types are in the boost::factory namespace, but this has been omitted for clarity):

struct foo { virtual ~foo() {} };
struct bar : foo { bar(int i) { std::cout << "bar() " << i << "\n"; } };
struct baz : foo { baz(int i) { std::cout << "baz() " << i << "\n"; } };

...

typedef factory<
    foo*(int)
  , map_type<std_map_adapter>
  , error_policy<factory_exception_policy>
  , id_type<std::string>
> myfactory_t;

myfactory_t f;

// Register a default (operator new) creator function
// for an implementation type
register_new_ptr<bar>(f, "bar");
register_new_ptr<baz>(f, "baz");

// Create objects through the factory
foo* fooimpl1 = f["bar"](1234);
foo* fooimpl2 = f["baz"](4321);

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

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

// No tagging
typedef factory<
    foo*(int)
  , std::string
  , std_map_adapter
  , factory_exception_policy
> myfactory_t;
// Mixed parameter order
typedef factory<
    foo*(int)
  , map_type<std_map_adapter>
  , error_policy<factory_exception_policy>
  , id_type<std::string>
> myfactory_t;
// All defaults
typedef factory<foo*(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<
    foo*(int)
  , error_policy<my_fancy_error_policy>
> myfactory_t;

Going back to the first example, there are a couple of utility-functions that make life easier when dealing with factories. The first one is register_new_ptr<type>, which creates a function object that allocates a new object of the given type on the heap using operator new whenever it is invoked (the type must be upcastable to whatever result type is given in the function signature, of course).

The second is register_wrapped_new_ptr<type>, which is used for result types that somehow wrap the returned object, such as std::auto_ptr and boost::shared_ptr. Given a factory with the signature std::auto_ptr<foo>(some-args), using register_wrapped_new_ptr<bar>(factory_instance, some-ident) will create a function object that creates a new bar instance and returns it as a std::auto_ptr<foo> value.

Both these utility-functions are simple wrappers around factory:: register_creator.

Now that we've got all kinds of juicy creators registered, we probably ought to actually do something with them. This is accomplished through using an overloaded operator[], which looks up the identifier in the internal map and returns a const-reference to the function object it resolves to (or invokes the error policy if none is found). This function object can then be invoked.

Abstract Factory Implementation

An abstract factory is signified by having multiple, abstract base types whose implementations can be specified at runtime and accessed through types (unlike the regular factory which has a single abstract base type and accessed through identifiers). The 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.

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

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

and any number of concrete factories, specifying the abstract factory and its corresponding concrete types (please note that the order of appearence of a concrete type must match its abstract one):

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

...

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

Through metaprogramming, the compiler will generate a full class hierarchy for the types, ensuring that each abstract type has its own virtual base, allowing you to decouple creation for a single type away from the type definition of the factory itself. To examplify: an instance of easy_enemy_factory_t can be upcasted to both enemy_factory_t as well as abstract_factory_field< abstract_soldier* >, abstract_factory_field< abstract_monster* > etc, the latter being part of the auto-generated hierarchy. See the documentation for a graphical example of the generated hierarchy.

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)
{
  f.create<abstract_soldier>();
}

void create_soldier_from_field(const abstract_factory_field<abstract_soldier*>& field)
{
  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);
}

Arguments and argument binding

It is often desirable to be able to pass arguments to an object creator. This can easily be done by specifying an abstract type as a function signature with a pointer return type and then supplying arguments to create(). How these are used is entirely up to the concrete factory. For the default, operator new creator, a constructor matching the arguments will be invoked with them.

typedef abstract_factory<
  abstract_foo*(const std::string&)
> foo_factory_t;

...

// f is an instance of foo_factory_t
std::auto_ptr<abstract_foo> obj(
  f.create<abstract_foo>("foo bar brawl")
);

It is also possible to (re)bind these arguments so that a completely different constructor is called with a different argument order/count.

For examples and more information, see the sections on argument passing and argument binding.

Prototypes

File: factory/prototype.hpp

A second way of handling the creation of concrete types is to use prototype-objects that are cloned. This is usually done by having the abstract base type declare a pure virtual clone() function which is then implemented in each concrete class to return a new object that is essentially a copy of itself.

Prototypes can be used on a per-concrete type basis using prototype<> (which has special meaning to the concrete factory), and are assigned via factory.prototype<abstract-type>(ptr).

Example showing a concrete factory using partial prototypes (note that prototypes are currently stored internally as boost::shared_ptrs):

typedef concrete_factory<
    enemy_factory_t
  , prototype<>, hard_monster, prototype<>
> mixed_enemy_factory_t;

mixed_enemy_factory_t my_factory;

// Assign the prototypes (raw or shared pointers)
my_factory.prototype<abstract_soldier>(new mad_soldier);
shared_ptr<abstract_beast> proto_beast(new mad_bunny):
my_factory.prototype<abstract_beast>(proto_beast);

// Now we can use create<Type>() as usual
std::auto_ptr<abstract_beast> beast(my_factory.create<abstract_beast>());

Dynamic functions

If none of the provided concrete creator mechanisms can do the job, it's possible to delegate the work to stored Boost.Functions by specifying the concrete type as dynamic<>. Its accessors match those of prototype (but with dynamic<type>() instead of prototype<type>(), of course). If arguments are passed to create(), they will be forwarded appropriately, allowing Boost.Bind et al to be used as usual.

Exception safety

All the factories are presumed exception-safe (as comforting as that may or may not sound). Transaction-style updates depend on the exception guarantees of the underlying factory container. When dealing with factory function objects that return raw pointers, immediately putting them in some managed container such as auto_ptr or shared_ptr is always a good idea to avoid leaks.

If you want to return smart pointers from the factory itself, version 1.2.1 adds support for specifying the returned type of an abstract type to be that of a smart pointer (currently requires that the pointer type has a nested element_type typedef to be recognized):

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;

Changing the types for the concrete factory/factories is not required. Same goes with the calls to create. These will all implicitly use the return-type specified for the abstract factory. It may be noted that even though a smart pointer is returned, all object creation is done with raw pointers internally (but done so with a strong exception safety guarantee), meaning that if a dynamic function is used for the creation, it will currently have to return a raw pointer, not a smart pointer.

Usage and feedback

Without going into any more implementational details, that about wraps it up. If this library seems like it could be useful to you, feel free to use it (covered by the Boost 1.0-license). However, again note that the code is still under development, may change based on feedback and absolutely no guarantees are given as to its correctness (although the unit tests should hopefully catch most problems).

I try to keep track of supported and unsupported compilers, but if you try to compile the code and it fails, please leave a comment or send me a mail, stating the compiler vendor, version and error.

Changes

See the factory documentation.

Acknowledgements

You guessed it; see the factory documentation.

Potential future stuff

This is just a kind of public TODO list of things that I might implement sooner or later, so that people can comment on their merits if they want.

  • Support C++0x rvalue references and perfect argument forwarding for create<type>(arg0,...,argn).
  • Support C++0x variadic templates to decrease amount of preprocessor usage
  • Better identification and documentation on dependencies, especially for changing preprocessed arities

Comments

Neal Binnendyk on December 4, 2008

This is very nice work. Thanks.

Joseph on March 15, 2009

I am currently very interested in Abstract Factory pattern implemented in C++. I think you will also find the tutorial downloadable from
http://downloads.sourceforg...
very interesting.

The code for this implementation is at
http://sourceforge.net/proj...

but seems much more advanced than the example code given in the tutorial and makes alot of use of meta-template programming

Joseph

mlimber on April 25, 2011

What's the status of this library viz-a-viz Boost? Have you submitted it for inclusion?

Also, you might be interested in this thread on automatic registration with Loki factory which could be added here, perhaps easier.

http://sourceforge.net/proj...

Best to paste the code into your editor and have it autoformat to make readable. Feel free to contact me if you're interested in including this.

Tor Brede Vekterli on May 6, 2011

Hi mlimber,

I posted some interest checks on the Boost dev mailing lists back in the days without it garnering much attention. I always did plan on throwing it out there again but I never got around to it. The woes of programmer laziness :)

Automatic registration would be a pretty neat feature. I'm thinking of getting the code up on sourceforge/github rather than it being squirreled away in dusty zipfiles on this site, so maybe some refactoring et al could be done in the process.

Post a comment