【问题标题】:C++: Generic factory that can call any constructor?C++:可以调用任何构造函数的通用工厂?
【发布时间】:2015-03-19 10:04:28
【问题描述】:

我正在尝试编写一个工厂类,它的标准接口如下所示:

Register<MyBase, MyDerived> g_regDerived("myderived");  // register to factory

现在调用:

auto* d = Factory<MyBase>::instance().create("myderived", 1, 2, 3);

将调用构造函数MyDerived(1,2,3)并返回一个指向创建对象的指针

这听起来像是 C++11 应该可以实现的,但我不知道该怎么做。
从标准类型擦除工厂开始:

template<typename BaseT>
class Factory {
public:
    static Factory* instance() {
        static Factory inst;
        return &inst;
    }
    template<typename T>
    void reg(const string& name) {
        m_stock[name].reset(new Creator<T>);
    }
    BaseT* create(const string& name) {
        return m_stock[name]->create();
    }
private:
    struct ICreator {
        virtual BaseT* create() = 0;
    };
    template<typename T>
    struct Creator : public ICreator {
        virtual BaseT* create() {
            return new T;
        }
    };
    std::map<string, std::unique_ptr<ICreator>> m_stock;
};

template<typename BaseT, typename T>
class Register {
public:
    Register(const QString& name) {
        Factory<BaseT>::instance()->reg<T>(name);
    }
};

这里的问题是,一旦你删除了创建对象的类型,你就不能再传递任意模板转发参数,因为你需要通过虚函数传递它们。

这个问题的答案:
How to pass a function pointer that points to constructor?
谈论类似的事情,但答案是通过一个特定于每个派生类的函数。我想直接使用类构造函数,不用写create()函数。

【问题讨论】:

  • 我认为做到这一点完美是不可能的;函数的参数实际上是compile-time,与其他一些语言相比,例如Java 或C#。 (他们可以通过反射动态调用函数)当然C++有compile-time反射,但是你想动态调用函数。我建议模仿这些运行时反射:你可以使用类似void *create(const std::vector&lt;std::pair&lt;void *, std::type_info *&gt;&gt; &amp;params); 的东西。您还可以提供宏或模板来帮助客户制作这个“创建”功能。
  • 如果你通过引入一个基本上是boost::any 列表的config 类来允许某种动态初始化,我会容易得多,并且总是允许作为未来的单个参数- 创建类。
  • create() 函数有什么问题?
  • @Adrian 你可以假设我的问题有充分的理由。用我已经说清楚的无用答案向它发送垃圾邮件对我没有帮助是很粗鲁的。
  • 我尝试并没有假设,但如果没有更多信息,您可能正在尝试做一些不必要和/或适得其反的事情。陈述你的理由可能会帮助人们想出你没有想到的解决方案,因为你离问题太近了。作为拥有 36k 代表的人,您应该知道这一点。至于你说我很粗鲁,因为你认为我在发送垃圾邮件 粗鲁。如果您想要更好的答案,请提出一个对一般社区更有用的问题。

标签: templates c++11 factory type-erasure


【解决方案1】:

我不知道你为什么讨厌写一个create() 函数。所以这是我实现的一个。

#include <iostream>
#include <utility>

using namespace std;

class C
{
public:
    virtual char const* whoAmI() const = 0;
};

class A : public C
{
public:
    A(int a1)
    {
        cout << "A(" << a1 << ")" << endl;
    }
    
    A(float a1)
    {
        cout << "A(" << a1 << ")" << endl;
    }
    
    virtual char const* whoAmI() const override
    {
        return "A";
    }
};

class B : public C
{
public:
    B(int a1)
    {
        cout << "B(" << a1 << ")" << endl;
    }
    
    B(float a1)
    {
        cout << "B(" << a1 << ")" << endl;
    }

    virtual char const* whoAmI() const override
    {
        return "B";
    }

};

template<typename BASET>
class Factory
{
public:
    // could use a is_base type trait test here
    template <typename T, typename...ARGs>
    static BASET* create(ARGs&&...args)
    {
        return new T(forward<ARGs>(args)...);
    }

};
int main()
{
   Factory<C> factory;
   C* a = factory.create<A>(1);
   C* b = factory.create<B>(1.0f);
   cout << a->whoAmI() << endl;
   cout << b->whoAmI() << endl;
   return 0;
}

注意:我没有做你所做的一切,我只是实现了 create 函数。我把最终的实现留给你。

这使用完美转发使 varidict 模板能够将任意数量的参数传递给构造函数。然后,您的注册函数可以为特定参数集存储特定模板实例的函数指针。

编辑

我忘记使用适当的forward&lt;ARGs&gt;(args)... 调用来实现完美转发。现已添加。

至于你认为这没有用,这里是你的工厂的完整实现,它使用完美的转发和 varidict 模板,允许特定数量的特定类型的参数用于特定工厂实例:

#include <string>
#include <map>
#include <memory>
#include <utility>
#include <iostream>

using namespace std;

    template<typename BaseT, typename...ARGs>
    class Factory {
    public:
        static Factory* instance() {
            static Factory inst;
            return &inst;
        }
        template<typename T>
        void reg(const string& name) {
            m_stock[name].reset(new Creator<T>);
        }
        BaseT* create(const string& name, ARGs&&...args) {
            return m_stock[name]->create(forward<ARGs>(args)...);
        }
    private:
        struct ICreator
        {
            virtual BaseT* create(ARGs&&...) = 0;
            
        };
        template<typename T>
        struct Creator : public ICreator {
            virtual BaseT* create(ARGs&&...args) override
            {
                return new T(forward<ARGs>(args)...);
            }
        };
        std::map<string, std::unique_ptr<ICreator>> m_stock;
    };
    
    template<typename BaseT, typename T, typename...ARGs>
    class Register {
    public:
        Register(const string& name) {
            auto instance = Factory<BaseT, ARGs...>::instance();
            instance->template reg<T>(name);
        }
    };

struct C
{
    virtual char const * whoAmI() const = 0;
};

struct A : public C
{
    A(int a1, int a2)
    {
        cout << "Creating A(" << a1 << ", " << a2 << ")" << endl;
    }
    
    virtual char const * whoAmI() const override
    {
        return "A";
    }
};

struct B : public C
{
    B(int b1, int b2)
    {
        cout << "Creating B(" << b1 << ", " << b2 << ")" << endl;
    }
    B(int b1, int b2, int b3)
    {
        cout << "Creating B(" << b1 << ", " << b2  << ", " << b3 << ")" << endl;
    }
    virtual char const * whoAmI() const override
    {
        return "B";
    }
};

typedef int I;
Register<C, A, I, I> a("a");
Register<C, B, I, I> b("b");
Register<C, B, I, I, I> b3("b");
int main()
{
    C* a = Factory<C, I, I>::instance()->create("a", 1, 2);
    C* b = Factory<C, I, I>::instance()->create("b", 3, 4);
    C* b3 = Factory<C, I, I, I>::instance()->create("b", 5, 6, 7);
    cout << "I am a " << a->whoAmI() << endl;
    cout << "I am a " << b->whoAmI() << endl;
    cout << "I am a " << b3->whoAmI() << endl;
    return 0;
}

这就是你想要的吗?如果您不想处理函数参数,请使用辅助模板函数为您推导出它们,如下所示:

template <typename BaseT, typename...ARGs>
BaseT* create(const string& name, ARGs&&...args)
{
    return Factory<C, ARGs...>::instance()->create(name, forward<ARGs>(args)...);
}

int main()
{
    C* a = create<C>("a", 1, 2);
    C* b = create<C>("b", 3, 4);
    C* b3 = create<C>("b", 3, 4, 5);
    cout << "I am a " << a->whoAmI() << endl;
    cout << "I am a " << b->whoAmI() << endl;
    cout << "I am a " << b3->whoAmI() << endl;
    return 0;
}

它的额外好处是允许通过明显的单个函数 API 使用多个构造函数签名(它看起来只是一个,但实际上是 N 其中 N 是数字您允许的不同签名)。这一切都可以通过这个online demo查看。

您仍然需要使用与我之前描述的相同的注册,可以通过宏来缩短。

如果这仍然不是你想要的,那么在你的问题中添加更多细节。

【讨论】:

  • 感谢您用无用的答案向问题发送垃圾邮件。现在请删除它
  • 嗯,最初的答案是无用的,因为它没有解决问题,部分陈述。编辑后的答案似乎确实有效。您正在为每组参数实例化 Factory,因此 Factory 不再是真正的单例。但是我可以制作像 MetaFactory 这样的东西,它将包含对所有不同工厂的引用,这些工​​厂将是一个单例(这对我来说很重要,因为我不想描述的一个不相关的原因)。谢谢你,我很抱歉:)
  • @shoosh,原来的答案是指向你想要去的方向。我不想为你做所有的工作。 ;)
猜你喜欢
  • 2013-06-16
  • 1970-01-01
  • 1970-01-01
  • 2014-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-15
  • 2022-11-09
相关资源
最近更新 更多