I've been trying to implement the Self Registering Factory pattern in my project and, after trying a bunch of ways to do it, I've settled for this solution.
Unfortunately, I've stumbled upon a problem where my code isn't compiling because my base class doesn't have any arguments to pass. There is a comment in the provided link detailing this exact issue but I must say that I don't understand why it doesn't work and how to make it work if possible.
Here is the error I get when compiling:
could not convert ‘std::make_unique(_Args&& ...) [with _Tp = ObjectA; _Args = {}; typename std::_MakeUniq<_Tp>::__single_object = std::unique_ptr<ObjectA, std::default_delete<ObjectA> >]()’ from ‘unique_ptr<ObjectA,default_delete<ObjectA>>’ to ‘unique_ptr<BaseClass,default_delete<BaseClass>>’
40 | return std::make_unique<T>(std::forward<Args>(args)...);
| ^
| |
| unique_ptr<ObjectA,default_delete<ObjectA>>
For the sake of clarity, I'll post the code and example of classes I'm trying to implement with it.
selfregisteringfactory.h
#include <memory>
#include <unordered_map>
#include <string>
#include <cstdlib>
#include <cxxabi.h>
std::string demangle(const char *name) {
int status = -4;
std::unique_ptr<char, void (*)(void*)> res{
abi::__cxa_demangle(name, NULL, NULL, &status), free};
return (status == 0) ? res.get() : name;
}
template<class Base, class... Args>
class SelfRegisteringFactory {
public:
template<class ... T>
static std::unique_ptr<Base> make(const std::string &name, T&&... args) {
return data().at(name)(std::forward<T>(args)...);
}
friend Base;
template <class T>
class Registrar : Base {
friend T;
static bool registerT() {
const auto name = demangle(typeid(T).name());
SelfRegisteringFactory::data()[name] = [](Args... args) -> std::unique_ptr<Base> {
return std::make_unique<T>(std::forward<Args>(args)...);
};
return true;
}
static bool registered;
private:
Registrar() : Base(Key{}) { (void)registered;};
};
private:
class Key {
Key(){};
template <class T> friend class Registrar;
};
using FuncType = std::unique_ptr<Base> (*)(Args...);
SelfRegisteringFactory() = default;
static std::unordered_map<std::string, FuncType> &data(){
static std::unordered_map<std::string, FuncType> s;
return s;
}
};
template <class Base, class... Args>
template <class T>
bool SelfRegisteringFactory<Base, Args...>::Registrar<T>::registered =
SelfRegisteringFactory<Base, Args...>::Registrar<T>::registerT();
baseclass.h
#include "selfregisteringfactory.h"
#include <string>
class BaseClass : public SelfRegisteringFactory<BaseClass>{
public:
BaseClass(Key){};
virtual ~BaseClass() = default;
virtual void process() = 0;
virtual std::string getType() = 0;
};
objecta.h
#include "baseclass.h"
class ObjectA: public BaseClass::Registrar<ObjectA>{
public:
ObjectA();
virtual ~ObjectA() = default;
virtual void process();
virtual std::string getType();
};
objecta.cpp
#include "objecta.h"
#include <iostream>
ObjectA::ObjectA(){
}
void ObjectA::process(){
std::cout << "This is a process." << std::endl;
}
std::string ObjectA::getType(){
return "ObjectA";
}
Update
As @AlanBirtles pointed out, I've missed the public when writting the Registrar class. The code compiles but when I test it with my unit tests it doesn't seems to register ObjectA. I'm getting the out-of-range exception from the .at().
Here's what my test file looks like:
selfregisteringfactory.test.cpp
#include "catch2/catch.hpp"
#include "catch/fakeit.hpp"
#include "baseclass.h"
using namespace fakeit;
TEST_CASE( "TEST SelfRegisteringFactory class." )
{
SECTION("Test if adding an OjbjectA to the factory is possible.")
{
auto objA = DeviceCommunication::make("ObjectA");
REQUIRE(objA ->getType() == "ObjectA");
}
}