Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a generic Factory that supports template covariance?

How to implement a generic Factory in C++14 that supports template covariance?

I want to achieve something like this:

std::shared_ptr<Factory<BaseClass>> factory = 
    std::make_shared<Factory<DerivedClass>>();

auto x = factory->create(arg1, arg2, arg3);

Note that in factory->create, you can pass any arguments to DerivedClass constructor. It is okay to assume that the BaseClass constructor and the DerivedClass are identical.


To avoid the XY Problem, the reason I need this is because I want to use dependency injection (boost::di) to achieve maximum testability.

For example, if there's a class A that creates Socket instances, I want it to depend on a Factory<ISocket> service. In the real code, I'd inject Factory<Socket>, and in the testing code, I'd inject Factory<Mock<ISocket>>, so I can test the A class without actually creating a real socket.


This is my current attempt:

template <typename T>
struct BaseFactory {
    virtual std::unique_ptr<T> create() = 0;
};

template <typename TInterface, typename TImplementation>
struct Factory : public BaseFactory<TInterface> {
    virtual std::unique_ptr<TInterface> create() override {
        return std::make_unique<TImplementation>();  
    }
};

The current usage is something like:

std::shared_ptr<BaseFactory<ISocket>> factory = 
    std::make_shared<Factory<ISocket, Socket>>();

auto x = factory->create();

Although not ideal (you need to specify the base class in Factory), this usage is fine for me and it works.

The next thing I need to add is support for constructor arguments. I've tried to add variadic template to create:

template <typename ...TArgs>
virtual std::unique_ptr<T> create() = 0;

... but it looks like you can't have virtual methods with templates.


  • Am I going in the right direction?
  • If yes, how should I add support for constructor arguments in my implementation?

Thank you!

like image 869
Alon Gubkin Avatar asked May 21 '16 11:05

Alon Gubkin


1 Answers

OK, I found one solution, but it's not pretty:

template <typename T, typename ...TArgs>
struct BaseFactory {
    virtual std::unique_ptr<T> create(TArgs&&... args) = 0;
};

template <typename TInterface, typename TImplementation, typename ...TArgs>
struct Factory : public BaseFactory<TInterface, TArgs...> {
    virtual std::unique_ptr<TInterface> create(TArgs&&... args) override {
        return std::make_unique<TImplementation>(std::forward<TArgs>(args)...);
    }
};

using ISocketFactory = BaseFactory<ISocket, int>;
using SocketFactory = Factory<ISocket, Socket, int>;

int main() {
    std::shared_ptr<ISocketFactory> socket_factory = 
        std::make_shared<SocketFactory>();

    std::unique_ptr<ISocket> socket = socket_factory->create(1234);
    socket->read();
    socket->write();
}

The idea is to pass the implementation class' constructor arguments in the BaseFactory and Factory templates. In this case, the Socket constructor should look something like:

Socket(int n);

Do you have any idea how to optimize this? (less boilerplate code)

like image 92
Alon Gubkin Avatar answered Oct 19 '22 23:10

Alon Gubkin