【问题标题】:C++ Factory Pattern with Heterogenous Constructor Constraint具有异构构造函数约束的 C++ 工厂模式
【发布时间】:2011-12-19 00:27:35
【问题描述】:

我正在实现一个 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 = Class1 和args = ["arg11, "arg12"]。

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

除了 Boost 之外还有其他我忽略的库可能有帮助吗?

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

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

【问题讨论】:

  • 为了以后参考,同质的反义词是异构
  • 嘿,Seth,根据 Merriam-Webster 词典,“不均匀”也是一个有效词,与“异构”的含义相似。我选择了前者,因为这也是 Boost.Function/Factory 文档的选择(请参阅我帖子中的链接)。
  • 是的,我知道,您几乎可以在任何单词中添加“in”或“un”,它仍然是一个单词。听起来很奇怪。
  • @Salman:我看到的关于 Merriam-Webster 的参考文献越多,我就越觉得它是一大堆垃圾。下次,试试牛津英语词典。
  • @DeadMG 我不在乎你对字典的选择有何看法。这与原始问题无关。任何一个词的选择都足够了。但是,如果出于文体原因,一个比另一个更受欢迎,那对我来说很好。但是请不要把它变成关于语言词典的讨论,这将很快退化为关于搜索引擎结果等的讨论。请坚持这个问题。谢谢!

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


【解决方案1】:

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

即:

// 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);
}

【讨论】:

  • 我当然考虑过类似的选项...即将 JSON 对象传递给静态工厂函数并让它返回实例。问题是我正在处理的代码库目前有 100 多个这样的类并且还在增长。因此,虽然这肯定是一个可能的解决方案,但我现在不敢称其为理想解决方案。
【解决方案2】:

为了实现你想要的,在你的代码中的某个时刻,一个巨大的switch-statement 来决定根据名称构造哪个类(实际上,switch 不会工作,因为你无法打开字符串 - 更像是一个很长的 if-else if)。

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

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

【讨论】:

  • 谢谢@bjorn & selbie。对于我的用例,最好的答案是代码生成。我的代码生成器是大约 100 行 Python 代码,可以生成大约 个有效的工厂函数。 IE。我必须按摩一些生成的代码。即使使用一个不太完美的代码生成器,我在完成这项任务时也非常高效。对于我的用例,可能仍然值得扩展 Boost.Functional/Factory。以后有待探索。 :) 仅供参考:C++ 头文件解析库:CppHeaderParser library,足以满足我的需求。
【解决方案3】:

我知道我来晚了,但是对于 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);
}

【讨论】:

  • 点评来源: 你能在你的答案中发布一些代码吗?链接可能会随着时间的推移而损坏。
猜你喜欢
  • 1970-01-01
  • 2015-11-27
  • 1970-01-01
  • 2019-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-13
  • 1970-01-01
相关资源
最近更新 更多