如何将参数传递给工厂元素构造函数?

3
我有一个类树,想要将其存储在fabric中。我知道如何为具有空或预定义构造函数的元素实现fabric,但我无法看到如何使其可能绕过未预定义构造函数参数签名的情况。因此,我们可以通过以不影响工厂类的方式注册不同类型到工厂。而我们的工厂如下所示: Factory.hpp:
#include <unordered_map>
#include <string>

template<class BaseType, class BaseTypeCode>
class Factory {
public:
    typedef BaseType * (*base_creator_fn)();
    typedef std::unordered_map<BaseTypeCode, base_creator_fn> registry_map;

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

    static BaseType * instantiate(BaseTypeCode const & name) {
        auto it = registry().find(name);
        return it == registry().end() ? nullptr : (it->second)();
    }

    virtual ~Factory() = default;        
};

template<class BaseClass,  class BaseTypeCode>
struct Registrar {
    Registrar(BaseTypeCode name, Factory<BaseClass>::base_creator_fn func) {
        Factory<BaseClass>::registry()[name] = func;
    }
};

因此问题是: 如何将typedef转换为typedef BaseType *(* base_creator_fn)(...);而不是当前的(),其次如何在实例化中转发参数,该参数将接受(BaseTypeCode const& name,...),最后如何从需要const数量的构造函数中获取新的base_creator_fn?
因此,我希望最终能得到以下用法示例: Example.hpp:
#include "Factory.hpp"

enum ExampleTypeCode { ExampleTypeCode_Simple = 1 };

class ExampleBase {
    virtual ~ExampleBase(){}
};

class Example : public ExampleBase {
    Example(int i) {}
    virtual ~Example(){}

    static Registrar<ExampleBase, ExampleTypeCode> registrar;
};

Example.cpp:

#include <boost/bind.hpp>
#include <boost/lambda.hpp>
#include "Example.hpp"

Registrar<Example, ExampleTypeCode> Example::registrar(ExampleTypeCode_Simple, boost::bind(boost::lambda::new_ptr<Example>, _firstVarArg));

Main.cpp

#include "Example.hpp"

int main() {
    ExampleBase * p = Base::instantiate(ExampleTypeCode_Simple, 1);
}

我的主要编译目标是最新的GCC和不幸的Visual Studio 2012,带有最新的服务包 - 因此C ++11子集相当有限但已经存在。

我认为你必须使用某种类型抹除。顺便问一下,你能用C++11吗? - Nawaz
顺便提一下,Example并不是从ExampleBase派生的。此外,在Example.cpp文件中似乎有一个拼写错误。请先修复这些问题。 - Nawaz
@Nswaz:修正了一些错别字,VS2012 是主要的痛点...所以还没有完全支持 C++11。 - DuckQueen
2个回答

8

Variadic模板在这里非常有用。我使用registry_map的第二个类型为void*,这样我们可以在注册表中存储任何类型的函数。下面是一个说明基本思路的最小代码:

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

    virtual ~Base() = default;

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

struct Registrar
{
    template<typename F>
    Registrar(std::string name, F func)
    {
        Base::registry()[name] = reinterpret_cast<void*>(func);
    }
};

以下为测试代码:我定义了两个派生类,其中一个类在创建时需要传入两个参数,而另一个类则不需要传入任何参数。

class DerivedExample : public Base
{
    static Registrar registrar;
public:
    static Base * create() { return new DerivedExample; }
    virtual void f() override { std::cout << "DerivedExample" << std::endl; }
};

Registrar DerivedExample::registrar("DerivedExample", DerivedExample::create);

class AnotherExample : public Base
{
    static Registrar registrar;
    int a;
    const char *b;
public:
    AnotherExample(int a, const char *b) : a(a), b(b) {}
    static Base * create(int a, const char *b) { return new AnotherExample(a,b); }
    virtual void f() override { std::cout << "AnotherExample. a = " << a << ", b = " << b << std::endl; }
};

Registrar AnotherExample::registrar("AnotherExample", AnotherExample::create);

int main()
{
    Base * p = Base::instantiate("DerivedExample");
    Base * q = Base::instantiate("AnotherExample", 10, "Mahfuza");
    p->f();
    q->f();

    delete p;
    delete q;
}

输出(在线演示):
DerivedExample
AnotherExample. a = 10, b = Mahfuza

希望能够帮到您。:-)

你好,非常好的回答,谢谢你提供 void* 的想法!请问你能否展示一下如何在 C++03/C 中实现可变长度参数转发? - DuckQueen
@DuckQueen:为了支持C++03中的可变参数,您需要在代码中添加重载函数模板。只需查看当前代码的工作方式,这将为您提供一些关于如何在C++03中完成此操作的想法。 - Nawaz

3

您可以将工厂与构建器组合以获得所需的行为。构建器将保存参数。

template<class BaseType, class BaseTypeCode, class BaseTypeBuilder>
class Factory {
public:
    typedef BaseType * (*base_creator_fn)(BaseTypeBuilder const &);
    //...
    static BaseType * instantiate(BaseTypeCode const & name,
                                  BaseTypeBuilder const & builder
                                  = BaseTypeBuilder()) {
        auto it = registry().find(name);
        return it == registry().end() ? nullptr : (it->second)(builder);
    }

    virtual ~Factory() = default;        
};

那么,对于您的示例,您需要创建一个构建器。构建器可以尽可能复杂,以帮助区分需要调用哪个构造函数:

class ExampleBuilder {
    boost::optional<int> i;

public:
    ExampleBuilder () {}

    void set_i (int x) { i = x; }

    static Example * create (const ExampleBuilder &builder) {
        if (builder.i) return new Example(builder.i);
        else return new Example();
    }
};

Registrar<Example, ExampleTypeCode, ExampleBuilder>
    Example::registrar(ExampleTypeCode_Simple, ExampleBuilder::create);

    ExampleBuilder build;
    build.set_i(1);
    ExampleBase * p = Base::instantiate(ExampleTypeCode_Simple, build);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接