I'd like to implement factory (or some other pattern) in a way that will allow me to compile the code without introducing type dependency.
enum CarType
{
BMW,
PORSCHE,
MERC
};
class CarFactory
{
public:
static Car* create(CarType type)
{
switch(type)
{
case BMW : return new BMWCar();
case PORSCHE : return new PorscheCar();
default : return new MercCar();
}
}
};
When I compile CarFactory, I need to include BMWCar, PorscheCar and MercCar as a part of my compilation/linking unit.
The way my codebase is setup, we may want to ship BMWCar only, or two or all three of them. So, I cannot make the create()
dependent on the type.
How can I adapt the factory pattern for this ? Also, I'd like to avoid doing ifdefs since this is just a sample of my problem. The real codebase is huge and is not a practical solution to ifdef the code.
Update: Also, I am not allowed to use:
- templates
- has to conform to c++ 98 standard
- cannot use boost
These are mostly due to customer build toolchain restrictions. I don't have a choice in changing these.
Instead of using a
switch
, you could use templates and specialization. The following example ships the implementation of bothBMWCar
andMercCar
but excludesPorscheCar
:Here is a complete example, C++98 style. I assumed that the list of possible car types is not known at compile time, so I changed the enum to a string.
cartype.hh:
cartype.cc:
bmw.cc (porsche.cc and merc.cc similar but not shown):
check.cc:
Compiling and running:
Note1: you cannot assume that all classes are registered until main has started, i.e. in other static initialization you may be doing.
Note2: This (in fact most solutions) may not work on its own if the
Car
implementations are in their own shared object libraries (.so). The linker will simply not put a dependency on that .so into the complete binary, unless the binary needs a symbol from that .so. So you need special linker options to force the linker to do so. This is mostly a problem for distributions that make--as-needed
the default (I'm looking at you, Ubuntu). Use--no-as-needed
or-Wl,--no-as-needed
to switch it back off, at least for the libraries containing car implementations.There are similar problems with static libraries (.a). A .a file is simply a collection of several .o files, and the linker will only include those .o files from the .a file that contain symbols that were previously undefined. The linker can be forced to consider a symbol undefined with
-u symbol_name
. But that is the mangled symbol name, so it is kind of hard to guess. One symbol that would work for that purpose in my example is_ZN6BMWCar10registeredE
, a.k.aBMW::registered
in unmangled form. But it is probably better to define a function with C linkage so you don't need to guess the mangled variable name:Then you don't have to guess the symbol name, and can just use
-u bmw_mark
. This has to be done in the same compilation unit as the other definitions forBMWCar
, so they end up in the same .o file.My solution:
I still need to figure out:
You could register the types dynamically to an array. The first solution that comes to my mind would be something like (You probably want a better design):
I usually do something similar to this:
And in each class implementation:
This works because each type initialize its own factory during the static initialization, using the
static Init
object.The huge pain in this code is required to avoid order-of-initialization fiasco: a naive implementation would simply use a static map in
CarFactory
. Unfortunately, there is no guarantee that theBMWCar::initializeBmwCar
constructor would run after the map inCarFactory
. Sometimes with some compilers might work, sometimes it might just crash. So the idea is to use a static function (getMap
) with a static variable (m
), that is guaranteed to be initialized the first timegetMap
is called.I know that
clang
/llvm
uses this pattern to register the optimization pass.Another solution that is more complicated, but much more flexible is to design a plugin system where each DLL implements one
car
type and exports oneCreateCar
function.Then you can collect all these
CreateCar
during initialization by dynamically loading the library and callingGetProcAddress
/dlsym
.Both solutions might be tricky to achieve on Windows, because (unless
Car
is abstract) the baseCar
implementation needs to go in its own library, and each plugin dll needs to link with that library.I'm not very good programmer in this thing. but, trying to give my best answer.