我正在实现一个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表示中特别构造,它如何影响问题的复杂性?
答案 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);
}