Object Factories are a useful abstraction of the object construction process. First you register, or subscribe, with a factory each class needing automated construction capability. A unique id is associated with each such class; you simply supply that id to the factory to get fresh object on demand. The instance is returned via a pointer to a base class with which you can work.
This article begins with a short review of the Object Factory idiom, and then demonstrates the "subscription problem," an issue that occurs when subscribing template classes with object factories. Finally, a small framework using template meta-programming is presented that deals with the problem.
In this article I will use a simplified version of Andrei Alexandrescu's Object Factory as described in his book, Modern C++ Design. The complete class and other useful idioms can be found also in Loki1. (Note that in this article the Loki namespace identifier is omitted.)
Our working definition of the object factory looks like this:
template <typename AbstractProduct, typename IdentifierType, typename ProductCreator = AbstractProduct * (*)()> class ObjectFactory { typedef ObjectFactory<AbstractProduct, IdentifierType, ProductCreator> ThisClass; public: AbstractProduct * create(const IdentifierType & id) { typename Associations::const_iterator i = this->associations_.find(id); if (this->associations_.end() != i) { return (i->second)(); } return 0; } bool subscribe(const IdentifierType & id, ProductCreator creator) { return this->associations_.insert(Associations::value_type(id, creator)).second; } private: typedef std::map<IdentifierType, ProductCreator> Associations; Associations associations_; };
By default, ProductCreator
is a function type that returns a pointer to AbstractProduct
. A typical creator function might look like the following.
template <typename Base, typename Derived> Base * createInstance() { return new Derived; }
Somewhere in your code you will have subscription statements which look like:
// SomeClass::ID() will return a unique ID for SomeClass factory->subscribe(SomeClass::ID(), createInstance<ISomeBase, SomeClass>);
Nice and easy. But it seems that the subscription issue is usually overlooked. The reason is that subscribing "regular" classes is easy. The problems begin when you need to subscribe template classes that use policies.
Many times users of those classes know at compile time the kind of policies they need and so the Object Factories need only to be able to create those template classes with the specific policies needed. But there are times where the exact combinations of policies are only known at runtime (e.g. highly configurable applications where users are able to choose the policies). In those cases the Object Factories should be able to create the template class with all possible combination of policies (except the ones that can't/shouldn't work together).
With many template classes and many policies this might be a problem to write and maintain.
Suppose, for example, that you're developing a game with a factory for Radar Stations:
typedef ObjectFactory<IRadar, std::string> RadarFactory;
The following statement subscribes a particular Radar
class with the factory.
factory->subscribe(Radar::ID(),createInstance<IRadar, Radar>);
But what happens if the Radar
is a template class which has some policies? For example, a Radar
might have a HidePolicy
with some implementations, e.g. it can not be hidden (NoHide
), hidden for periods of time (TimeoutHiding
) or some other complicated hidden policy (CHP). Radar objects might also have a DefensePolicy
: NoDefense
, BulletProofed
and ShellProofed
, so the Radar
declaration might look like this:
template <typename HidePolicy, typename DefensePolicy> class Radar : public IRadar {...};
Suppose also that the user of the factory doesn't know at compile time the exact policies that will be needed. For example, a player might be eligible for any combination of policies based on the resources she earned during the game.
This change is very dramatic as far as the subscription is concerned. Now we need to subscribe the Radar
with all possible combinations of its policies. We end up writing 9 statements. Here is one:
factory->subscribe(Radar<NoHide, NoDefense>::ID(), createInstance<IRadar, Radar<NoHide, NoDefense> >);
"Yes", you might say, "subscription becomes a tedious job, but this is done only once, so it is not too bad". But this is not always the situation. During development and maintenance things tend to change. For example what happens if you need to add a new policy implementation (e.g. AtomicProofed
)? In this case you need to remember to add three subscriptions (one for each HidePolicy
). It gets even worse if Radar
is now supposed to have a new type of policy such as RadiationPolicy
that has also three implementations. Now, you have to rewrite the subscriptions and end up with 36 statements!
Large applications that might have several factories and template classes that need to be subscribed to might easily end up with hundreds of statements. For example, imagine you have a template class with four policies, where each policy has four implementations. In this case only you'll have to write 256 subscription statements.
The obvious thing to do here is to change the template policies into class hierarchies with interfaces as their base classes. For example:
class IDefense {....}; class BulletProofed: public IDefense{....};
This means that we will need to add DefenseFactory
and to subscribe the concrete Defense
classes.
When a new policy implementation is introduced, all we have to do is add a new subscription for the right factory. In the worst case scenario, when new type of policy is introduced, we will need to add a new factory and write the new subscriptions statements. The down side of this solution is that you have many more factories and you have to add some code to coordinate the creation of the class with its policies (and also deal with the id issue mentioned earlier).
This might be a good solution if interfaces are really needed, but using interfaces instead of templates to sidestep the subscription problem seems a bit awkward. After all, what if we really do need template classes? For example, many of the methods in the policies can be inlined while Interfaces will make those methods virtual which will end-up in performance hit.
Is there a way to use templates and still have an elegant way to subscribe them with factories?
There is a way to get template metaprogramming to do the dirty work for us. Face it-the compiler is faster, more accurate and doesn't need a day off after writing 256 subscription statements. We will describe a family of "Factory Subscribers" to do the job. This article will demonstrate one member of the family, FactorySubscriber_2
, which we will use for the Radar
example.
First, here is FactorySubscriber_2
definition:
template <template <typename A1, typename A2> class C, typename Policies_1, typename Policies_2, typename IPC, typename Factory, typename CMW> struct FactorySubscriber_2;
The "2" indicates that this factory subscriber knows how to subscribe a class that has two template arguments.
FactorySubscriber_2
declares:
Policies_1
and Policies_2
that stand for the two lists of policies (NoHide etc.).createInstance
that we saw before. Instead we will use a wrapper that will be able to fetch the creation method for every type. The nice thing here is that the wrapper only needs to appear in the base class. The nice thing here is that by using the wrapper class we don't need to know about those distinct types, rather we only pass the Base
class (e.g. IRadar
) to that wrapper.Here is an example of a possible CMW for the classic new/delete creator:
template <typename Base> struct NewCreatorWrapper { // Creates an instance of Derived // and returns that instance as pointer to its Base template <typename Derived> static Base * createMethod() { return new Derived; } };
The FactorySubscriber_2
needs to work with two unknown lists of policies. This is easily achievable with Typelists
.2
Now we need to specialize the FactorySubscriber_2
for the various scenarios it should support. Here's the first one:
// Specialized version which is for the case where we have two Typelists template <template <typename A1, typename A2 > class C, typename H1, typename T1, typename H2, typename T2, typename IPC, typename Factory, typename CMW> struct FactorySubscriber_2<C, Typelist<H1,T1>, Typelist<H2,T2,> IPC, Factory, CMW> { static bool subscribe(Factory * f) { return FactorySubscriber_2<C, H1, H2, IPC, Factory, CMW:>:subscribe(f) && FactorySubscriber_2<C, H1, T2, IPC, Factory, CMW:>:subscribe(f) && FactorySubscriber_2<C, T1, H2, IPC, Factory, CMW:>:subscribe(f) && FactorySubscriber_2<C, T1, T2, IPC, Factory, CMW:>:subscribe(f); } };
This specialized version applies when the FactorySubscriber_2
receives two Typelist
s; each list has at least one item (not including the NullType
that is placed on every Typelist
's tail).
This FactorySubscriber_2
has a static subscribe method that will subscribe all the possible combinations:
The second specialization of FactorySubscriber_2
is for the case where the first group of policies contains exactly one policy while the second group contains more than one policy.
template <template <typename A1, typename A2 > class C, typename P, typename H1, typename T1, typename Factory, typename IPC, typename CMW> struct FactorySubscriber_2<C, P, Typelist<H1, T1>, IPC, Factory, CMW> { static bool subscribe(Factory * f) { return FactorySubscriber_2<C, P, H1, IPC, Factory, CMW:>:subscribe(f) && FactorySubscriber_2<C, P, T1, IPC, Factory, CMW:>:subscribe(f); } };
In this specialization, the FactorySubscriber_2
will: 1. Subscribe the only policy (P) with the head of Typelist 2 2. Subscribe the only policy (P) with the tail of Typelist 2.
We have another specialized FactorySubscriber_2
that is very much the same as this one except that this time the first group of policies contains more than one policy while the second group has exactly one:
template <template <typename A1, typename A2 > class C, typename H1, typename T1, typename P, typename IPC, typename Factory, typename CMW> struct FactorySubscriber_2<C, Typelist<H1, T1>, P, IPC, Factory, CMW> { static bool subscribe(Factory * f) { return FactorySubscriber_2<C, H1, P, IPC, Factory, CMW:>:subscribe(f) && FactorySubscriber_2<C, T1, P, IPC, Factory, CMW:>:subscribe(f); } };
The next specialization is the stop condition. This version of FactorySubscriber_2
is the one that can subscribe the class in the factory. It assumes that it receives exactly one policy in each group:
template <template <typename A1, typename A2 >class C, typename P1, typename P2, typename IPC, typename Factory, typename CMW> struct FactorySubscriber_2 { private: // Typedefs to save typing typedef FactorySubscriber_2<C, P1, P2, IPC, Factory, CMW >ThisClass; typedef TYPELIST_2(P1, P2) PoliciesTL; // First, we find if at least one policy is a NullType enum { nullPolicies = IsNullType<P1>::value || IsNullType<P2>::value }; // If - nullPolicies == true then legalPolicies is set to false // by using Int2Type<false>::value // Else - We check the policies against the Illegal Policies Combinations // (IPC) and set its (IPCChecker<IPC, PoliciesTL>::value) to be // the outcome of this check enum { legalPolicies = Select<nullPolicies, Int2Type<false,> IPCChecker<IPC, PoliciesTL >>::Result::value }; public: static bool subscribe(Factory * f) { // Activate the right subscribe method based on legalPolicies value return ThisClass::subscribe<C, P1, P2, Factory, CMW>(f, Int2Type<legalPolicies>()); } private: // Overloaded template method for the case where legalPolicies is false template <template <typename A1_1, typename A2_1 > class C_1, typename P1_1, typename P2_1, typename Factory_1, typename CMW_1> static bool subscribe(Factory_1 * factory, Int2Type<false>) { return true; } // Overloaded template method for the case where legalPolicies is true template <template <typename A1_1, typename A2_1> class C_1, typename P1_1, typename P2_1, typename Factory_1, typename CMW_1> static bool subscribe(Factory_1 * factory, Int2Type<true>) { // Subscribe the class in the factory // The CMW assumes to have a 'createMethod' method return factory->subscribe( C_1<P1_1, P2_1:>::ID(), CMW_1::createMethod<C_1<P1_1, P2_1>> ); } };
The subscribe
method above uses another private subscribe
method to subscribe the class C<P1, P2>
to the factory. But before doing so, this class needs to make sure that the policies, P1
and P2
, are legal.
There are two reasons why they might not be legal. First, typelists have a NullType
type as their last element. This means that this specialized version might be instantiated with a NullType
in one or two of its arguments which are useless for class 'C'. Second, even if neither policy is NullType
, we still need to check them against the IPC. Only if the policies pass both checks do we subscribe the class C<P1, P2> to the factory.
The decisions are made by two enums. The important one, legalPolicies
, depends on nullPolicies
and so the compiler will first need to determine its value. nullPolicies
will be true if at least one policy is a NullType
. The IsNullType
specialization does that job:
template <typename T> struct IsNullType { enum { value = false }; }; template <> struct IsNullType<NullType> { enum { value = true }; };
Next, the compiler will determine the value of legalPolicies
. In case that nullPolicies
is true than legalPolicies
is false (value of Int2Type<false>
). Otherwise the value is the outcome of applying IPCChecker
(explained shortly) on both IPC and the two policies (wrapped as a Typelist
).
Instantiating IPCChecker
, however, is relevant only if nullPolicies
is false. Loki's Select()
function resolves that issue. Select has three arguments: a Boolean value and two types. The result is a type, either the first one (if the Boolean value is true) or the second one. This means that the compiler will need to evaluate only the value of the selected type (::Result::value
). This idiom is important because it makes sure that the compiler doesn't evaluate both branches. For example, the following code seems harmless, but it's not.
enum { legalPolicies = nullPolicies ? Int2Type<false>::value : IPCChecker<IPC, PoliciesTL >>::value };
Although the value of nullPolicies
is known to the compiler, it will still need to evaluate both values, which will increase compilation time, not to mention that there is no guarantee that both branches will compile. Once legalPolicies
value is determined, it is used to call one of the private subscribe methods.
The private section of the class contains two overloaded of subscribe that have a very similar signature. The only difference is that one expects Int2Type<false>
while the other receives Int2Type<true>
. Loki's Int2Type
transforms an integer or a Boolean value into a distinct type.
The first private subscribe method will be instantiated by the compiler if legalPolicies
is false (either one or two of the policies are NullType
or they form an illegal combination). This method is a no-op.
The second private subscribe method will be instantiated by the compiler if legalPolicies
is true. In this case the class will be subscribed to the factory (at last!). Note that this method uses the CMW to obtain the pointer to the creator function for the class subscribed with the factory. Note that the compiler will compile only the method that is called.
Note also that we can't use an if statement to do the subscription, because the compiler would need to compile both branches of the if, which is problematic. For example the compiler will try to find an ID method for class C
when P_1
is a NullType
, so the following example is wrong:
static bool subscribe(FACTORY * factory) { // This is wrong since the compiler will need to instantiate both // branches of the 'if' statement if (policiesAreLegal) return factory->subscribe( C_1<P_1, P_2>::ID(), CMW::createMethod<C<P_1, P_2> >() ); else return true; }
The IPCChecker
class checks policies for all factory subscribers:
template <typename IPC, typename CP> struct IPCChecker;
It checks the policies (CP) against all illegal policies combinations (IPC).
There are some things that this framework assumes:
Typelist
of Typelists
. This is because the number of combination is unknown as the number of policies in each combination (examples to follow).NullType
represents an empty list which means that there are no restrictions.EmptyType
3 as a wild card that symbolizes any type. Typelist
.As before, we specialize the class to support all possible scenarios.
The first specialization (see below) is for the case where there is no illegal combination list, or for the case where we've reached the last element in IPC after checking CP against all the Typelists
in IPC. Either way the result is true, which means that the combination is legal.
template <typename PC> struct IPCChecker<NullType, PC> { enum { value = true }; };
The second specialization (see below) relies on the fact a Typelist
is itself a type. This applies when the first element of the IPC (which is a Typelist
) matches (i.e., is the same type as) the combination of policies that we check. In this case the result is false, meaning that PC is an illegal combination.
template <typename CP, typename T> struct IPCChecker<Typelist<CP, T>, CP> { enum { value = false }; };
The next specialization below is for the case where the first element of the IPC (which is a Typelist
) doesn't match (i.e., is not the same type as) the combination of policies that we check. In this case we still need to further check those Typelists
because they might not be the same in terms of the type system, but they might be the same for the application. This might happen if the Typelist PC1
contains EmptyType
that made PC1
a different type from PC2
:
template <typename PC1, typename T, typename PC2> struct IPCChecker<Typelist<PC1, T>, PC2> { // Compare the first Typelist to the compared policies combination // If there is a match - this is an illegal policies combination // Else - check the policies combination against the rest of // the Typelist typedef typename Select<IsSamePoliciesList<PC1, PC2>::value, Int2Type<false>, IPCChecker<T, PC2> >::Result Result; enum { value = Result::value }; };
This specialization uses IsSamePoliciesList
(covered next) to check the unmatched Typelists (PC1
and PC2
). If the returned value is true (there is a match) than we don't need to continue. Otherwise we need to check the policies against the rest (i.e. T) of the illegal policies combination list. Again, to save compilation time, we use here Loki::Select
to make sure that the compiler instantiates one branch.
Now we are left with defining IsSamePoliciesList
.
The declaration:
template <typename TL_1, typename TL_2> struct IsSamePoliciesList;
This specialization deals with the case where we start by comparing two lists of policies. Here we check the result of comparing the two heads of the lists, if they match, than we continue to check the rest of the Typelists, otherwise the result is false, i.e., there is no match:
template <typename H1, typename T1, typename H2, typename T2> struct IsSamePoliciesList<Typelist<H1, T1>, Typelist<H2, T2> > { // Check the first type in each Typelist // If there is a match - continue and check the tail of each list // Else - The outcome is false typedef typename Select<IsSamePoliciesList<H1, H2>::value, IsSamePoliciesList<T1, T2>, Int2Type<false> >::Result Result; enum { value = Result::value }; };
A few more specializations and we're almost done...
* A specialization for the case where we compare two Typelists which are the same. This specialization is needed to distinguish this case from the next one:
template <typename H, typename T> struct IsSamePoliciesList<Typelist<H, T>, Typelist<H, T> > { enum { value = true }; };
* A specialization for the case where the two compared policies are of the same type:
template <typename P> struct IsSamePoliciesList<P, P> { enum { value = true }; };
* A specialization for the case where the two policies are not of the same type:
template <typename P1, typename P2> struct IsSamePoliciesList { enum { value = false }; };
* A specialization for the case where we've reached the end of each Typelist
:
template <> struct IsSamePoliciesList<NullType, NullType> { enum { value = true }; };
* Specializations for the case where one list is longer than the other
template <typename P> struct IsSamePoliciesList<NullType, P> { enum { value = false }; }; template <typename P> struct IsSamePoliciesList<P, NullType> { enum { value = false }; };
* A specialization for the case where a policy (P) is compared against an EmptyType
(which symbolizes any type) and so the result is true:
template <typename P> struct IsSamePoliciesList<EmptyType, P> { enum { value = true }; };
Although FactorySubscribers
ease the subscription process, it would be more convenient and intuitive for users to interact with the factory rather than working directly with the FactorySubscribers
. We can add some methods to the factory for that purpose:
// A new method in the factory to subscribe template class with two // template arguments template <template <typename A1, typename A2> class C, typename Policies_1, typename Policies_2, typename IPC, typename CMW> bool subscribe() { return FactorySubscriber_2<C, Policies_1, Policies_2, IPC, ThisClass, CMW>::subscribe(this); } // A new method in the factory to subscribe template class with three // template arguments. // This method uses FactorySubscriber_3 which is not described in this // article). template <template <typename A1, typename A2> class C, typename Policies_1, typename Policies_2, typename Policies_3, typename IPC, typename CMW> bool subscribe() { return FactorySubscriber_3<C, Policies_1, Policies_2, Policies_3, IPC, ThisClass, CMW>::subscribe(this); }
Now let's use this framework to subscribe the Radar
along with its two lists of policies. This statement will subscribe all 9 combinations.
factory->subscribe<Radar, TYPELIST_3(NoHide, TimeoutHidding, CHP), TYPELIST_3(NoDefense, BulletProofed, ShellProofed), NullType, // No restrictions NewCreatorWrapper<IRadar> >();
Some macros will clear things up even more:
// No restrictions #define RADAR_IPC NullType #define RADAR_HIDE_POLICIES TYPELIST_3(NoHide, TimeoutHidding, CHP) #define RADAR_DEFENSE_POLICIES TYPELIST_3(NoDefense, BulletProofed,\ ShellProofed) factory->subscribe<Radar, RADAR_HIDE_POLICIES, RADAR_DEFENSE_POLICIES, RADAR_IPC, NewCreatorWrapper<IRadar> >();
Adding a new AtomicProofed
policy implementation will be done like this:
#define RADAR_DEFENSE_POLICIES TYPELIST_4(NoDefense, BulletProofed,\ ShellProofed, AtomicProofed)
TYPELIST_3
was changed into TYPELIST_4
and AtomicProofed
was added to that group. That's it. The subscription statement stays the same.
Adding a new type of Policies (Radiation
) is easy too. We add a new macro:
#define RADAR_RADIATION_POLICIES TYPELIST_3(NoRadiation, LightRadiation, \ DangerousRadiation)
.... and change the subscription statement:
// This subscribe method will use the FactorySubscriber_3. factory->subscribe<Radar, RADAR_HIDE_POLICIES, RADAR_DEFENSE_POLICIES, RADAR_RADIATION_POLICIES, RADAR_IPC, NewCreatorWrapper<IRadar> >();
Restricting combinations of policies can now be as easy as changing a macro:
#define RADAR_IPC TYPELIST_1(TYPELIST_3(NoHide, ShellProofed,\ DangerousRadiation))
Here we declare that we have one illegal combination. Note that we need to use a Typelist of Typelists. Another option:
#define RADAR_IPC TYPELIST_2(TYPELIST_3(NoHide, ShellProofed, \ DangerousRadiation), \ TYPELIST_3(EmptyType,AtomicProofed,\ DangerousRadiation))
In this case we have two illegal combinations; the second one means that AtomicProofed
can't work together with DangerousRadiation
no matter what the hidden policy is (Loki's EmptyType
is used here as a wild card to symbolize any type). Note also that there is no impact on the subscription statement.
The framework described in this article lets you easily write and change subscription statements of template classes in factories. The downside is that many classes are produced along the way-classes that don't do anything. But on the other hand most of these classes are likely to be optimized away by the compiler.
Writing the code for more complicated subscribers (e.g. FactorySubscriber_3
, FactorySubscriber_4
) is not trivial. But you can comfort yourself that once you've done writing that code you've gained a lot: You have a framework that can work with any factory and any class and its lists of policies, and you have all the code that does the subscription written and maintained in one place. Using this framework makes subscription code elegant, easy
Thanks to all the reviewers for their input. Thanks to Chuck Allison, for editing this article. Thanks also to Andrei Alexandrescu for his comments and encouragement.
[1] http://loki-lib.sourceforge.net
[2] Andrei Alexandrescu - Modern C++ Design
[3] As its name implies, it is just an empty class. It can be found in Loki.
Have an opinion? Readers have already posted 6 comments about this article. Why not add yours?
-
Artima provides consulting and training services to help you make the most of Scala, reactive
and functional programming, enterprise systems, big data, and testing.