Search code examples
c++oopdesign-patternsfactory-patternc++98

implement factory pattern for products with conditional compiling


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.


Solution

  • 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:

    #include <map>
    #include <string>
    #include <vector>
    
    struct Car {
      virtual std::string type() = 0;
      virtual ~Car() {}
    };
    
    // Factory
    class CarFactory
    {
      typedef std::map<std::string, Car *(*)()> Registry;
      static Registry &registry();
    public:
      static std::vector<std::string> getRegisteredTypes();
      static void registerCarType(std::string type, Car *(*creator)());
      static Car* create(std::string type);
    };
    

    cartype.cc:

    #include <map>
    #include <string>
    #include <vector>
    
    #include "cartype.hh"
    
    // Factory
    CarFactory::Registry &CarFactory::registry()
    {
      static std::map<std::string, Car *(*)()> r;
      return r;
    }
    
    std::vector<std::string> CarFactory::getRegisteredTypes()
    {
      static const Registry &reg = registry();
      std::vector<std::string> types;
      types.reserve(reg.size());
      Registry::const_iterator end = reg.end();
      for(Registry::const_iterator it = reg.begin(); it != end; ++it)
        types.push_back(it->first);
      return types;
    }
    
    void CarFactory::registerCarType(std::string type, Car *(*creator)())
    {
      registry()[type] = creator;
    }
    
    Car* CarFactory::create(std::string type)
    {
      static const Registry &reg = registry();
      Registry::const_iterator result = reg.find(type);
      if(result != reg.end())
        return result->second();
      throw "Unregistered car type";
    }
    

    bmw.cc (porsche.cc and merc.cc similar but not shown):

    #include <string>
    
    #include "cartype.hh"
    
    // BMW
    class BMWCar : public Car
    {
      static const bool registered;
      static Car *create() { return new BMWCar; }
    public:
      virtual std::string type() { return "BMW"; }
    };
    const bool BMWCar::registered =
      (CarFactory::registerCarType("BMW", BMWCar::create),
       true);
    

    check.cc:

    #include <iostream>
    #include <memory>
    #include <ostream>
    #include <string>
    #include <vector>
    
    #include "cartype.hh"
    
    int main()
    {
      // all car types should be registered when we enter main
      std::vector<std::string> types = CarFactory::getRegisteredTypes();
      for(std::size_t i = 0; i < types.size(); ++i)
      {
        std::auto_ptr<Car> car(CarFactory::create(types[i]));
        std::cout << "Wanted: " << types[i] << ", Got: " << car->type() << std::endl;
      }
    }
    

    Compiling and running:

    -*- mode: compilation; default-directory: "/tmp/" -*-
    Compilation started at Tue Aug  4 01:24:51
    
    set -ex; g++ -std=c++98 -g -O3 -Wall check.cc cartype.cc bmw.cc porsche.cc -o check; ./check
    + g++ -std=c++98 -g -O3 -Wall check.cc cartype.cc bmw.cc porsche.cc -o check
    + ./check
    Wanted: BMW, Got: BMW
    Wanted: PORSCHE, Got: Porsche
    
    Compilation finished at Tue Aug  4 01:24:54
    

    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.a BMW::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:

    extern "C" void bmw_mark() { }
    

    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 for BMWCar, so they end up in the same .o file.