Factory pattern in modern c++
With this article I am exploring the implementation of the factory pattern in modern C++, using features like varadic template functions, perfect forwarding, constexpr if statements and smart pointers. In particular I highlight how the if constexpr feature comes to the rescue to achieve a flexible factory method.
In the factory pattern (Design Patterns: Elements of Reusable Object-Oriented Software, E. Gamma, R. Helm, R. Johnson, J. Vlissides) the construction of of an object of some base class, with an associated class hierarchy, is delegated to a factory object or method. This factory, beyond other operations, specializes the creation of the object based on the arguments given, by calling the appropriate constructor of a class belonging to the hierarchy.
As a toy example for this article I consider a very simple Animal hierarchy having three derived classes: a Cat, a Bug and and UnknownFormOfLife, all inheriting the pure abstract method action. The main aspect used in this article is the fact that the three derived constructors require a different number of parameters: a name (string) and a fur colour (string) for a cat, a name (string), skin colour (string) and number of legs (int) for a bug, and no arguments for an unknown form of life, as can bee seen in the code attached.
The simplest factory
As Scott Meyers explained in his topical book on modern C++ (Effective Modern C++, Scott Meyers), a factory method returning a unique_ptr to an instantiation of a class hierarchy is an efficient and flexible factory. This is because a unique_ptr is the most efficient and economical type of smart pointer, it is not copyable but can easily move-construct a smart_pointer when returned by a factory function. Actually, the std::make_unique (C++14 onwards) is the most primitive and fundamental type of factory method, as illustrated in this code snippet, in the main method (line 75-83).
The std::make_unique perfect forwards its arguments to the constructor of the class used as template parameter. A possible implementation of it (again Scott Meyers’) could be in the form of this varadic function:
template<typename T, typename... Ts> std::unique_ptr<T> make_unique(Ts&&... params) { return std::unique_ptr<T>(new T(std::forward<Ts>(params)...)); }
This function takes a normal template parameter T to determine the class of the object to be constructed, and a parameters’ pack Ts for the universal references to be forwarded to the proper constructor, using the varadic signature introduced in C++11.
A simple but awkward factory method
Let’s assume that the factory needs to do some processing apart from constructing objects. For example, it could involve some logging, an interaction with an object cache to check if the same animal has been constructed before and hence can be recycled, or any other bespoke processing that might produce a side-effect. In this case we need to write a bespoke factory method. To make things simpler for the following examples, this extra processing will consists simply of counting the number of times the factory has been called. Here is a code sample.
The difference with respect to the previous code sample is from line 79 onwards. A Factory with a method makeAnimal is declared, and it contains a static counter to keep track of the number of times it is invoked. A unique_ptr<Animal> is first created with no content and then reset with the properly constructed animal, while the counter is incremented.
The reason why this section is titled an awkward factory method is because, as seen in lines 108 to 113, we need to feed the factory method with as many arguments as the union of all parameters required by all animal constructors: for a cat we can pass only three arguments, because the number of legs is defaulted to 0, but for the unknown form of life we need to pass some dummy extra arguments. Besides, the declaration of Factory::makeAnimal needs to have as many input parameters as the union of all constructor input parameters. This is clearly unwieldy when the class hierarchy is wide and the constructors have different signatures.
We need something more flexible, adaptable to different constructor signatures. An obvious candidate is a varadic template function as illustrated in the following sections.
A wrong varadic templated factory method that does not work
In the code here, we have an implementation that does not work. In fact, the varadic template function implemented in lines 95-114 is instantiated from the main in lines 120, 122 and 124, with three, four and zero arguments, respectively. In all these cases the varadic function needs to compile in all the if..if else..else branches. So when three arguments are given, the second and third branch will not compile, when four are given the first and third will not compile and when none are given the first two will not compile. The only way to fix this compilation issue, while keeping this implementation of the factory, is to add dummy constructors for Cat and Bug, i.e by un-commenting lines 41,44,66 and 69. This is BAD.
Unfortunately partial specialization for the varadic template function does not help because partial template specialization is only allowed for varadic class templates, which gives us another idea for a possible solution…
A varadic templated factory functor: still awkward
If we can’t have a partial specialization for varadic template function, why not using a functor instead, a callable class that acts like a function by declaring an operator()? Let’s look at the implementation here.
The good news here is that we have removed the need for dummy constructors in the animal hierarchy. However we have literally swept the problem under the carpet, because the price of a clean class hierarchy is paid by two extra costs:
- The need to do a partial specialization for all different signatures of constructors,
- A very ugly usage pattern in the main method, with the need of passing a list of template parameters, and of constructing an instance of the class with the default constructor invoked by ().
Even if a cleaner implementation can be achieved, there is really no gain with this implementation: the idiosyncrasy of the class hierarchy’s different constructors simply appears somewhere else.
And finally…the best implementation: flexible and economical
Here is the best implementation, using again a Factory struct to highlight a possible usage with a side-effect (increasing a counter).
At the core of this implementation is the use of the construct if constexpr and the type trait is_same_v.
The advantage of if constexpr, as compared to the runtime if used in the first attempt, is that the compiler considers only the if branches that satisfy the constexpr condition: all other branches are excluded from compilation. In this way, for example, when I want to construct a Cat object I don’t have to worry that the two arguments are inconsistent with the Bug constructor signature. So when the constexpr condition tells the compiler that template argument A is the same class as the Cat class, only that relevant branch is compiled, and the other conditional branched discarded.
At the same time the condition required inside an if constexpr expression has to be a constexpr expression: a simple comparison like animal_type==”cat” would not do because the variable animal_type would not be constexpr. Hence we rely on the type trait is_same_v<A,B> which is equivalent to the more verbose metafunction is_same<A,B>::value.
The beauty of this implementation is that it resembles very closely the usage of the std::make_unique template function. This is clear by comparing the following statements:
//using std::make_unique auto ptr1 = std::make_unique<Cat>("Charlie","black"); //using our make function auto ptr2 = Factory::makeAnimal<Cat>("Charlie","black");
In conclusion a combined use of if constexpr, type traits and varadic templated functions with perfect forwarding results in the most flexible and economical factory method.