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.
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.