-3

I have a class named Creation in both Creation.cc and Creation.h, and there are bunch of createA, createB, createC...etc. functions inside this class, all of them will return a pointer type.

I have a lot of modelA.cc, modelB.cc, modelC.cc ...etc. files, and all of them have included Creation.h.

Since whenever I make a new model x (make a new modelx.cc), I need to add the corresponding createx in Creation.h, which will make all model.cc files being compiled again.

All the createA, createB, createC functions have same parameter list but different input values and implementation, which are based on their model.cc.

My goal is that I don't want to recompile all model.cc when adding a new createx function.

Thanks.

Jenny
  • 76
  • 8

1 Answers1

1

A common strategy is to have the factory contain a registration method. Once a class is registered then a call is made to the factory to get an actual instance.

The C++17 example below allows sub-classes of the 'interface' to have different parameters in the call for creation. 'createInstance' is tasked with constructing an instance for a particular class.

The Any class was taken from here. As noted in the link, the createInstance call is quite particular about the input arguments matching the method signature.

#include <iostream>
#include <functional>
#include <map>
#include <string>
#include <any>
#include <functional>
#include <map>
#include <string>
#include <iostream>

struct interface
{
};


template<typename Ret>
struct AnyCallable
{
    AnyCallable() {}
    template<typename F>
    AnyCallable(F&& fun) : AnyCallable(std::function(fun)) {}
    template<typename ... Args>
    AnyCallable(std::function<Ret(Args...)> fun) : m_any(fun) {}
    template<typename ... Args>
    Ret operator()(Args&& ... args) 
    { 
        return std::invoke(std::any_cast<std::function<Ret(Args...)>>(m_any), std::forward<Args>(args)...); 
    }
    std::any m_any;
};

struct factory
{
   std::map<std::string,  AnyCallable<interface>> registry;
   void registerClass(std::string const & class_name, AnyCallable<interface> function)
   {
      registry[class_name] = function;
   }

   template<typename ... Args>
   interface createInstance(std::string const & class_name, Args && ... args)
   {
      if(registry.find(class_name) == registry.end())
      {
         throw "No class found";
      }
      return registry[class_name](std::forward<Args>(args)...); 
   }
};


struct A:public interface
{
   A() 
   {
      std::cout << "Create A" << std::endl;
   }

   static interface createInstance(int t)
   {
      return A();
   } 

   static void registerWithFactory(factory& f)
   {
      f.registerClass("A",&createInstance);
   }
};


struct B:public interface
{
   B() 
   {
      std::cout << "Create B" << std::endl;
   }

   static interface createInstance(std::tuple<int, double> t)
   {
      return B();
   } 

   static void registerWithFactory(factory& f)
   {
      f.registerClass("B",&createInstance);
   }
};

int main(int argc, char* argv[])
{
    factory f;

    A::registerWithFactory(f);
    B::registerWithFactory(f);
    try {
        interface a = f.createInstance("A",1);
        interface b = f.createInstance("B",std::tuple{1,1.0});
        interface c = f.createInstance("C");
    }
    catch(...)
    {
       std::cout << "createInstance failed" << std::endl;
    }
    return 0;
}

All the members of the factory will descend from 'interface'. The 'factory' will allow registration of new class that are not yet created. In the example A and B exists but C does not. In the future C can be added without recompiling the existing code.

There are a variety of patterns that expand on this theme.

Matthew Fisher
  • 2,258
  • 2
  • 14
  • 23
  • What if the registered functions are not like A(), and B(), but they have the same input parameter list but not same parameter value and function body? – Jenny Sep 07 '18 at 14:55
  • createInstance can be a variadic, https://stackoverflow.com/questions/1657883/variable-number-of-arguments-in-c or std::bind can be used to create an entry in the registry if the arguments are fixed. – Matthew Fisher Sep 07 '18 at 15:08
  • Suppose B::CreateInstance() is like return CreateB(int, double,string), can I give the value for those int double and string in main function? Thanks in advance. Since I'm new to stack overflow, the system seems to prohibit me from changing the upvote score. – Jenny Sep 07 '18 at 15:27
  • I'd suggest attaching the identifier to the class, f.createInstance(A::id) so it's only in one place. Nice demo though, I'll upvote now. – Kenny Ostrom Sep 07 '18 at 17:21
  • Yes an associated id is nice. Oftentimes, the creation is data driven however so passing a number/string is simple. For example, a row from the database that contains a column determining business logic for the other columns like column 'compression' with values 'gzip', 'zip', 'raw' might be feed to the CompressorFactory. Is this way the instance type can be completely unknown at compile time, but of course is constrained to the registered set. – Matthew Fisher Sep 07 '18 at 17:25
  • Updated for variadic createInstance, much harder than I was hoping. – Matthew Fisher Sep 09 '18 at 00:54