【发布时间】:2019-03-07 13:48:37
【问题描述】:
我想为一组派生类实现一个通用工厂机制,它不仅可以通用地实现工厂函数来创建该类的对象,还可以实现其他模板类的创建者,这些模板类的模板参数之一派生类。
理想情况下,解决方案将仅使用 C++17 功能(无依赖关系)。
考虑这个例子
#include <iostream>
#include <string>
#include <memory>
struct Foo {
virtual ~Foo() = default;
virtual void hello() = 0;
};
struct FooA: Foo {
static constexpr char const* name = "A";
void hello() override { std::cout << "Hello " << name << std::endl; }
};
struct FooB: Foo {
static constexpr char const* name = "B";
void hello() override { std::cout << "Hello " << name << std::endl; }
};
struct FooC: Foo {
static constexpr char const* name = "C";
void hello() override { std::cout << "Hello " << name << std::endl; }
};
struct BarInterface {
virtual ~BarInterface() = default;
virtual void world() = 0;
};
template <class T>
struct Bar: BarInterface {
void world() { std::cout << "World " << T::name << std::endl; }
};
std::unique_ptr<Foo> foo_factory(const std::string& name) {
if (name == FooA::name) {
return std::make_unique<FooA>();
} else if (name == FooB::name) {
return std::make_unique<FooB>();
} else if (name == FooC::name) {
return std::make_unique<FooC>();
} else {
return {};
}
}
std::unique_ptr<BarInterface> bar_factory(const std::string& foo_name) {
if (foo_name == FooA::name) {
return std::make_unique<Bar<FooA>>();
} else if (foo_name == FooB::name) {
return std::make_unique<Bar<FooB>>();
} else if (foo_name == FooC::name) {
return std::make_unique<Bar<FooC>>();
} else {
return {};
}
}
int main()
{
auto foo = foo_factory("A");
foo->hello();
auto bar = bar_factory("C");
bar->world();
}
我正在寻找一种机制,允许我在不列出所有类的情况下同时实现 foo_factory 和 bar_factory,这样一旦我添加例如 FooD 作为额外的派生类,它们就不需要更新.理想情况下,不同的 Foo 衍生品会以某种方式“自我注册”,但将它们全部放在一个中心位置也是可以接受的。
编辑:
基于cmets/answers的一些澄清:
- 在我的情况下,有必要使用(类似于)字符串来调用工厂,因为工厂的调用者使用
Foo/BarInterface的多态性,即他们不知道具体的派生类。另一方面,在 Bar 中,我们希望使用派生 Foo 类的模板方法并促进内联,这就是为什么我们确实需要模板派生Bar类(而不是通过某些基类接口访问 Foo 对象)。 - 我们可以假设所有派生的 Foo 类都定义在一个地方(因此,如果需要,我们可以在同一地方将它们全部列出一次的手动注册)。但是,他们不知道 Bar 的存在,实际上我们有多个不同的类,例如
BarInterface和Bar。因此,我们不能创建 Bar 的“构造函数对象”并将它们保存在映射中,就像我们为foo_factory所做的那样。我认为需要的是所有派生 Foo 类型的某种“编译时映射”(或列表),这样在定义 bar_factory 时,编译器可以迭代它们,但我不知道该怎么做...
编辑2:
证明相关的其他约束during discussion:
-
模板和模板模板: Foo 实际上是模板(具有单个类参数),而 Bar 是模板模板,将具体的 Foo 作为模板参数。 Foo 模板没有特化,并且都具有相同的“名称”,因此查询任何具体类型都可以。特别是
SpecificFoo<double>::name始终有效。 @Julius 的答案已经扩展以促进这一点。对于@Yakk,可能也可以这样做(但我需要一些时间来详细弄清楚)。 -
灵活的条形工厂代码: Bar 的工厂不仅仅是调用构造函数。它还传递一些参数并进行一些类型转换(特别是,它可能有 Foo 引用,应该是
dynamic_cast到相应的具体派生 Foo)。因此,允许在 bar_factory 定义期间内联编写此代码的解决方案对我来说似乎最易读。 @Julius 的回答在这里效果很好,即使带有元组的循环代码有点冗长。 -
让列出 Foo 的“单一位置”更加简单: 从目前的答案来看,我相信我要走的路是拥有一个 foo 类型的编译时列表和一种迭代它们的方法。有两个答案在一个中心位置定义了 Foo 类型(或模板)列表(使用
types模板或元组),这已经很棒了。但是,由于其他原因,我已经在同一个中心位置有一个宏调用列表,每个 foo 一个,例如DECLARE_FOO(FooA, "A") DECLARE_FOO(FooB, "B") ...。FooTypes的声明能否以某种方式利用这一点,所以我不必再次列出它们?我猜这样的类型列表不能迭代声明(附加到已经存在的列表),或者可以吗?在没有那个的情况下,可能有一些宏观魔法是可能的。也许总是重新定义并因此附加到DECLARE_FOO调用中的预处理器列表,然后最后一些“迭代循环”来定义FooTypes类型列表。 IIRC boost 预处理器具有循环列表的功能(尽管我不想要 boost 依赖项)。
对于更多 context,您可以将不同的 Foo 及其模板参数视为类似于 Eigen::Matrix<Scalar> 的类,而 Bar 是与 Ceres 一起使用的成本函子。 bar 工厂将ceres::AutoDiffCostFunction<CostFunctor<SpecificFoo>, ...> 之类的对象作为ceres::CostFunction* 指针返回。
编辑3:
根据@Julius 的回答,我创建了一个解决方案,该解决方案适用于作为模板和模板模板的 Bars。我怀疑可以使用可变参数模板模板将bar_tmpl_factory 和bar_ttmpl_factory 统一为一个函数(这是一回事吗?)。
待办事项:
结合bar_tmpl_factory和bar_ttmpl_factory上面的点Making the "single place" listing the Foos even simpler-
可能会用@Yakk 的types模板替换元组的使用(但在某种程度上,可以在循环的调用点内联定义创建者函数,覆盖所有 foo 类型)。
我认为问题已回答,如果有的话,以上几点应该是单独的问题。
【问题讨论】:
-
staticstd::map<std::string, std::function<std::unique_ptr<Foo>()>>并在派生类附近注册? -
但是必须通过派生类的
std::string标识符吗?将其作为模板类型(foo_factory<FooA>()和bar_factory<FooA>())传递怎么样? -
@Jarod42,这适用于
foo_factory,但不适用于bar_factory,对吧?请注意,有多个不同的BarInterface像我需要工厂的类。 -
@max66 是的,它需要处理诸如传递字符串(或者可能是多态
Foo*指针)之类的东西。 bar_factory 的调用者不知道派生的 Foo 类。
标签: c++ templates polymorphism c++17 factory