factory method for multiple constructors in the same class

81 Views Asked by At

I have this code which is an adaptation from How to pass arguments to factory elements constructors? to work with smart pointers.

#include <unordered_map>
#include <string>
#include <iostream>
#include <memory>
 
class Base;

class myFactory
{
public:
    typedef std::unordered_map<std::string, void*> registry_map;
 
    virtual ~myFactory() = default;
 
    static registry_map & registry()
    {
        static registry_map impl;
        return impl;
    }

    template<typename ...T>
    static std::shared_ptr<Base> instantiate(std::string const & name, T&&...args)
    {
        auto it = registry().find(name);
        if ( it == registry().end()) return 0;
        typedef std::shared_ptr<Base> (*create_type)(T...);
        auto create_fun = reinterpret_cast<create_type>(it->second);
        return create_fun(args...);
    }

    template<typename F>
    static bool sign(const std::string& name, F func)
    {
        registry()[name] = reinterpret_cast<void*>(func);
        return true;
    }
};
 
class Base: public myFactory
{
    public:
        virtual void f() = 0;
        virtual ~Base() = default;
};
 
class DerivedExample : public Base
{
private:
    static bool sign;

public:
    DerivedExample(int a, int b){std::cout << a << b << std::endl;}
    DerivedExample() = default;
    
    static std::shared_ptr<Base> create() { return std::make_shared<DerivedExample>();}
    static std::shared_ptr<Base> create(int a, int b) { return std::make_shared<DerivedExample>(a,b);}

    virtual void f() override { std::cout << "DerivedExample" << std::endl; }
};
 
bool DerivedExample::sign = DerivedExample::myFactory::sign("DerivedExample", DerivedExample::create());
bool DerivedExample::sign = DerivedExample::myFactory::sign("DerivedExample", DerivedExample::create(int a, int b)); // redefinition


int main()
{
    std::shared_ptr<Base> p1 = Base::instantiate("DerivedExample");
    std::shared_ptr<Base> p2 = Base::instantiate("DerivedExample", 1, 2);
    p1->f();
    p2->f();

}

This implementation does not let me register 2 constructors for the factory because of a redefinition of signboolean. Is there a way to fix the code in order to register multiple create functions for the the same class?

Best regards

2

There are 2 best solutions below

1
Jarod42 On BEST ANSWER

The bool sign is used for "auto" registration at global scope. You might just add extra variable:

class DerivedExample : public Base
{
private:
    static bool sign1;
    static bool sign2;
    // ...
};

bool DerivedExample::sign1 = DerivedExample::myFactory::sign("DerivedExample1", static_cast<std::shared_ptr<Base> (*)()>(&DerivedExample::create));
bool DerivedExample::sign2 = DerivedExample::myFactory::sign("DerivedExample2", static_cast<std::shared_ptr<Base> (*)(int, int)>(&DerivedExample::create));

The way to register was wrong BTW, especially when overloads are involved.

Demo

0
Oersted On

A way to fix you code:

#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>

class Base;

class myFactory {
   public:
    typedef std::unordered_map<std::string, void*> registry_map;

    virtual ~myFactory() = default;

    static registry_map& registry() {
        static registry_map impl;
        return impl;
    }

    // here needing to pass by value in order to match your create function
    // signature
    template <typename... T>
    static std::shared_ptr<Base> instantiate(std::string const& name,
                                             T... args) {
        auto it = registry().find(name);
        if (it == registry().end()) return 0;
        typedef std::shared_ptr<Base> (*create_type)(T...);
        auto create_fun = reinterpret_cast<create_type>(it->second);
        return create_fun(args...);
    }

    template <typename F>
    static bool sign(const std::string& name, F func) {
        registry()[name] = reinterpret_cast<void*>(func);
        return true;
    }
};

class Base : public myFactory {
   public:
    virtual void f() = 0;
    virtual ~Base() = default;
};

class DerivedExample : public Base {
   public:
    DerivedExample(int a, int b) { std::cout << a << " " << b << std::endl; }
    DerivedExample() { std::cout << "default" << std::endl; };

    static std::shared_ptr<Base> create() {
        return std::make_shared<DerivedExample>();
    }
    static std::shared_ptr<Base> create(int a, int b) {
        return std::make_shared<DerivedExample>(a, b);
    }

    virtual void f() override { std::cout << "DerivedExample" << std::endl; }

   private:
    // using one bool per function to register
    static bool sign1;
    static bool sign2;
    // properly getting the overloaded function addresses
    static std::shared_ptr<Base> (*pcreate1)();
    static std::shared_ptr<Base> (*pcreate2)(int, int);
};

std::shared_ptr<Base> (*DerivedExample::pcreate1)() = &DerivedExample::create;
std::shared_ptr<Base> (*DerivedExample::pcreate2)(int, int) =
    &DerivedExample::create;
// correctly passing the create function addresses
bool DerivedExample::sign1 =
    DerivedExample::sign("DerivedExample", DerivedExample::pcreate1);
bool DerivedExample::sign2 =
    DerivedExample::sign("DerivedExample2", DerivedExample::pcreate2);

int main() {
    std::shared_ptr<Base> p1 = Base::instantiate("DerivedExample");
    std::shared_ptr<Base> p2 = Base::instantiate("DerivedExample2", 1, 2);
    p1->f();
    p2->f();
}

Live

But I'm not sure it's the best design. I would prefer to register only one for the inherited class (though the above proposal shows that it is possible to do otherwise). Besides, the variadic signature was wrong, I fixed it by replacing forwarding-references by pass-by-value but it's not necessarily what you wanted to do.

[EDIT] Here is a solution without variadic and with a single registration, following my advice in comment.

#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>

class Base;

class myFactory {
   public:
    typedef std::unordered_map<std::string, void *> registry_map;

    virtual ~myFactory() = default;

    static registry_map &registry() {
        static registry_map impl;
        return impl;
    }

    // here needing to pass by value in order to match your create function
    // signature
    template <typename... T>
    static std::shared_ptr<Base> instantiate(std::string const &name,
                                             T... args) {
        auto it = registry().find(name);
        if (it == registry().end()) return 0;
        typedef std::shared_ptr<Base> (*create_type)(T...);
        auto create_fun = reinterpret_cast<create_type>(it->second);
        return create_fun(args...);
    }

    template <typename F>
    static bool reg(const std::string &name, F func) {
        registry()[name] = reinterpret_cast<void *>(func);
        return true;
    }
};

class Base : public myFactory {
   public:
    virtual void f() = 0;
    virtual ~Base() = default;
};

// abstract base parameter class
struct Parameter {
    virtual ~Parameter() = 0;
};
Parameter::~Parameter() = default;

// parameter for constructor from void
struct VoidParameter : public Parameter {};

// parameter for constructor from two ints
struct TwoIntParameter : public Parameter {
    int a;
    int b;
    TwoIntParameter(int first, int second) : a(first), b(second) {}
};

class DerivedExample : public Base {
   public:
    DerivedExample(int a, int b) { std::cout << a << " " << b << std::endl; }
    DerivedExample() { std::cout << "default" << std::endl; };

    static std::shared_ptr<Base> create(const Parameter *param) {
        if (dynamic_cast<const VoidParameter *>(param)) {
            return std::make_shared<DerivedExample>();
        }
        if (dynamic_cast<const TwoIntParameter *>(param)) {
            TwoIntParameter const *ptr =
                dynamic_cast<const TwoIntParameter *>(param);
            return std::make_shared<DerivedExample>(ptr->a, ptr->b);
        }
        return nullptr;
    }

    virtual void f() override { std::cout << "DerivedExample" << std::endl; }

   private:
    // using one bool per function to register
    static bool sign;
};

// correctly passing the create function addresses
bool DerivedExample::sign =
    DerivedExample::reg("DerivedExample", &DerivedExample::create);

int main() {
    VoidParameter v;
    TwoIntParameter ti{1, 2};
    std::shared_ptr<Base> p1 = Base::instantiate("DerivedExample", &v);
    std::shared_ptr<Base> p2 = Base::instantiate("DerivedExample", &ti);
    p1->f();
    p2->f();
}

Live

You'll have to create a derived parameter type per constructor you want to support. You may avoid repetition according to what you exactly want to achieve.