具有异构构造函数约束的C ++工厂模式

时间:2011-12-19 00:27:35

标签: c++ templates boost dispatcher factory-pattern

我正在实现一个C ++程序,它可以在给定输入文件的情况下以编程方式实例化对象,该文件提供传递给构造函数的类名和参数。

这些类派生自一个公共基类,但它们的构造函数签名各不相同。

声明如下:

class Base { ... }
class Class1 : Base { Class1(int a1, int a2); }
class Class2 : Base { Class2(int a1, int a2, int a3); }
... and so on...

参数类型不必是int,实际上它们可以是任何内置类型或复杂的自定义类型。

程序输入看起来像JSON格式:

[
  { "Class1": ["arg11", "arg12"] },
  { "Class2": ["arg21", "arg22", "arg23"] },
  ...and so on...
]

通过Boost.Functional/Factory的文档阅读它似乎可以解决我的问题,因为它不是因为在我的应用程序中构造函数签名变化(异构性约束)。 Boost.Function / Factory的方法是规范化构造函数签名,但这在我的应用程序中是不可能的。

在像Python这样的动态语言中,这将是相当微不足道的:obj = klass(*args)其中klass = Class1args = ["arg11, "arg12"]

那么如何用C ++中的异构约束来实现工厂模式呢?

除了Boost之外还有其他我忽略过的可能有帮助的图书馆吗?

是否可以实现这一点,以便唯一的依赖是标准库(即没有Boost)?

此外,在构造函数参数是复杂类型的情况下,它必须从其JSON表示中特别构造,它如何影响问题的复杂性?

3 个答案:

答案 0 :(得分:5)

您是否考虑过为每个知道如何根据从文件中读取的参数“数组”构造对象的类的工厂方法。

那是:

// declared "static" in header file
Class1* Class1::FactoryCreate(int argc, const char** argv)
{
    if (argc != 2)
        return NULL; // error

    int a1 = atoi(argv[0]);
    int a2 = atoi(argv[1]);
    return new Class1(a1, a2, a3);
}

// declared "static" in header file
Class2* Class2::FactoryCreate(int argc, const char** argv)
{
    if (argc != 3)
        return NULL; // error
    int a1 = atoi(argv[0]);
    int a2 = atoi(argv[1]);
    int a3 = atoi(argv[2]);
    return new Class2(a1, a2, a3);
}

答案 1 :(得分:1)

要实现您想要的功能,您需要在代码中的某个位置使用巨型switch语句来决定根据名称构建哪个类(实际上,switch不会工作,因为你无法打开字符串 - 更像是一个很长的if - else if)。

此外,您显示的表示似乎不包含有关构造函数参数类型的任何信息。如果您的类具有可使用相同数量的参数调用的多个构造函数,则可能会出现问题。

最后,我认为最好是使用@selbies answer之类的东西,但是使用代码生成为你生成构造代码。

答案 2 :(得分:0)

我知道我来晚了一些,但是C ++ 17 heterogeneous_factory有一个很好的现代解决方案。主要思想是将类型擦除与std :: any一起使用,该类型允许您存储不同的构造函数特征类型。该库是特定的,但是经过全面记录和测试。

这里是了解所描述方法的基本思想的最小示例:

template <class BaseT>
class Factory
{
    using BasePtrT = std::unique_ptr<BaseT>;
public:
    template<class RegistredT, typename... Args>
    void registerType(const std::string& name)
    {
        using CreatorTraitT = std::function<BasePtrT(Args...)>;
        CreatorTraitT trait = [](Args... args) {
            return std::make_unique<RegistredT>(args...);
        };
        _traits.emplace(name, trait);
    }

    template<typename... Args>
    BasePtrT create(const std::string& name, Args... args)
    {
        using CreatorTraitT = std::function<BasePtrT(Args...)>;
        const auto found_it = _traits.find(name);
        if (found_it == _traits.end()) {
            return nullptr;
        }
        try {
            auto creator = std::any_cast<CreatorTraitT>(found_it->second);
            return creator(std::forward<Args>(args)...);
        }
        catch (const std::bad_any_cast&) {}
        return nullptr;
    }

private:
    std::map<std::string, std::any> _traits;
};

struct Interface
{
    virtual ~Interface() = default;
};

struct Concrete : Interface
{
    Concrete(int a) {};
};

struct ConcreteSecond : Interface
{
    ConcreteSecond(int a, int b) {};
};

int main() {
    Factory<Interface> factory;
    factory.registerType<Concrete, int>("concrete");
    factory.registerType<ConcreteSecond, int, int>("second_concrete");
    assert(factory.create("concrete", 1) != nullptr);
    assert(factory.create("concrete") == nullptr);
    assert(factory.create("second_concrete", 1) == nullptr);
    assert(factory.create("second_concrete", 1, 2) != nullptr);
}