![]() |
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:
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;
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!" }
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);
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 |
|---|---|
|
|
Type used for function storage. Defaults to |
|
|
|
|
|
The type given in |
|
|
The type given in |
|
|
Boost.CallTraits
parameter type for |
|
|
Boost.CallTraits
parameter type for |
|
|
Internal map type, as given by |
Table 1.2. Factory methods (see above table for type-reference)
|
Method |
Description |
|---|---|
|
|
Registers the mapping id -> function. Returns |
|
|
Returns a reference to the stored function that |
|
|
Removes the mapping that |
|
|
Returns |
|
|
Returns a |