c++ - C++17 中的通用工厂机制
问题描述
我想为一组派生类实现一个通用工厂机制,它不仅可以通用地实现工厂函数来创建该类的对象,还可以实现其他模板类的创建者,这些模板类将其中一个派生类作为模板参数.
理想情况下,解决方案将仅使用 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 衍生品会以某种方式“自我注册”,但将它们全部放在一个中心位置也是可以接受的。
编辑:
基于评论/答案的一些澄清:
- 在我的情况下,有必要使用(类似于)字符串来调用工厂,因为工厂的调用者使用带有
Foo
/的多态性BarInterface
,即他们不知道具体的派生类。另一方面,在 Bar 中,我们希望使用派生 Foo 类的模板方法并促进内联,这就是我们真正需要模板派生Bar
类的原因(而不是通过某些基类接口访问 Foo 对象)。 - 我们可以假设所有派生的 Foo 类都定义在一个地方(因此,如果需要,我们可以在同一个地方将它们全部列出一次的手动注册)。但是,他们不知道 Bar 的存在,实际上我们有多个不同的类,例如
BarInterface
和Bar
。因此,我们不能创建 Bar 的“构造函数对象”并将它们保存在地图中,就像我们可以为foo_factory
. 我认为需要的是所有派生 Foo 类型的某种“编译时映射”(或列表),这样在定义 bar_factory 时,编译器可以迭代它们,但我不知道该怎么做...
编辑2:
在讨论期间证明相关的其他约束:
- 模板和模板模板: Foo 实际上是模板(具有单个类参数),而 Bar 是模板模板,将具体的 Foo 作为模板参数。Foo 模板没有特化,并且都具有相同的“名称”,因此查询任何具体类型都可以。特别
SpecificFoo<double>::name
是总是有效的。@Julius 的答案已经扩展以促进这一点。对于@Yakk,可能也可以这样做(但我需要一些时间来详细弄清楚)。 - 灵活的 bar 工厂代码: Bar 的工厂不仅仅是调用构造函数。它还传递一些参数并进行一些类型转换(特别是,它可能具有应该
dynamic_cast
指向相应的具体派生 Foo 的 Foo 引用)。因此,允许在 bar_factory 定义期间内联编写此代码的解决方案对我来说似乎最易读。@Julius 的答案在这里很有效,即使带有元组的循环代码有点冗长。 - 使列出 Foos 的“单一位置”更加简单:从到目前为止的答案来看,我相信我要走的路是拥有一个 foo 类型的编译时列表和一种迭代它们的方法。有两个答案在一个中心位置(使用
types
模板或使用元组)定义了 Foo 类型(或模板)的列表,这已经很棒了。但是,由于其他原因,我已经在同一个中心位置有一个宏调用列表,每个 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 类型)。
我认为问题已得到回答,如果有的话,以上几点应该是单独的问题。
解决方案
template<class...Ts>struct types_t {};
template<class...Ts>constexpr types_t<Ts...> types{};
这让我们可以在没有元组开销的情况下处理类型包。
template<class T>
struct tag_t { using type=T;
template<class...Ts>
constexpr decltype(auto) operator()(Ts&&...ts)const {
return T{}(std::forward<Ts>(ts)...);
}
};
template<class T>
constexpr tag_t<T> tag{};
这让我们可以将类型作为值来处理。
现在类型标签映射是一个接受类型标签并返回另一个类型标签的函数。
template<template<class...>class Z>
struct template_tag_map {
template<class In>
constexpr decltype(auto) operator()(In in_tag)const{
return tag< Z< typename decltype(in_tag)::type > >;
}
};
这需要一个模板类型映射并将其转换为标签映射。
template<class R=void, class Test, class Op, class T0 >
R type_switch( Test&&, Op&& op, T0&&t0 ) {
return static_cast<R>(op(std::forward<T0>(t0)));
}
template<class R=void, class Test, class Op, class T0, class...Ts >
auto type_switch( Test&& test, Op&& op, T0&& t0, Ts&&...ts )
{
if (test(t0)) return static_cast<R>(op(std::forward<T0>(t0)));
return type_switch<R>( test, op, std::forward<Ts>(ts)... );
}
这让我们可以在一堆类型上测试一个条件,并在“成功”的那个上运行一个操作。
template<class R, class maker_map, class types>
struct named_factory_t;
template<class R, class maker_map, class...Ts>
struct named_factory_t<R, maker_map, types_t<Ts...>>
{
template<class... Args>
auto operator()( std::string_view sv, Args&&... args ) const {
return type_switch<R>(
[&sv](auto tag) { return decltype(tag)::type::name == sv; },
[&](auto tag) { return maker_map{}(tag)(std::forward<Args>(args)...); },
tag<Ts>...
);
}
};
现在我们要创建一些模板类的共享指针。
struct shared_ptr_maker {
template<class Tag>
constexpr auto operator()(Tag ttag) {
using T=typename decltype(ttag)::type;
return [](auto&&...args){ return std::make_shared<T>(decltype(args)(args)...); };
}
};
这样就可以给共享指针一个类型。
template<class Second, class First>
struct compose {
template<class...Args>
constexpr decltype(auto) operator()(Args&&...args) const {
return Second{}(First{}( std::forward<Args>(args)... ));
}
};
现在我们可以在编译时组合函数对象。
接下来接线。
using Foos = types_t<FooA, FooB, FooC>;
constexpr named_factory_t<std::shared_ptr<Foo>, shared_ptr_maker, Foos> make_foos;
constexpr named_factory_t<std::shared_ptr<BarInterface>, compose< shared_ptr_maker, template_tag_map<Bar> >, Foos> make_bars;
和完成。
最初的设计实际上是带有 lambda 的c++20,而不是那些struct
s forshared_ptr_maker
等。
两者都make_foos
具有make_bars
零运行时状态。
推荐阅读
- pine-script - 点后 16 位的十进制数显示奇怪的行为
- fftw - 未定义对“rfftw3d_f77_create_plan_”的引用
- php - URL 重定向和转换 /(斜杠)和 . (点)到 - (连字符)
- cloud-foundry - CloudFoundry - 基于空间名称的条件环境变量
- javascript - 如何通过道具获取数组并渲染?
- php - 如何获取数组中每个月的记录数
- flutter - 使用来自 REST API 的同步更新 Scaffold 主体
- javascript - Material ui onClose 作为 disableBackdropClick 的替代品
- javascript - 防止 ajax 404 错误出现在浏览器的控制台中
- excel - 在 22 年 6 月 IE 11 生命周期结束后,VBA 的 Internet 控制功能会起作用吗?