[英]Generic factory mechanism in C++17
I would like to implement a generic factory mechanism for a set of derived classes that allows me to generically implement not only a factory function to create objects of that class, but also creators of other template classes which take as template arguments one of the derived classes. 我想为一组派生类实现一种通用工厂机制,该机制使我不仅可以通用地实现一个工厂函数来创建该类的对象,而且还可以实现其他模板类的创建者,这些其他模板类的创建者将其中一个派生类作为模板参数。
Ideally a solution would only use C++17 features (no dependencies). 理想情况下,解决方案仅使用C ++ 17功能(不依赖)。
Consider this example 考虑这个例子
#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();
}
I am looking for a mechanism that would allow me to implement both foo_factory
and bar_factory
without listing all classes, such that they do not need to be updated once I add for example FooD
as an additional derived class. 我正在寻找一种机制,使我可以在不列出所有类的情况下实现foo_factory
和bar_factory
,这样,一旦我将FooD
添加为其他派生类,就不必更新它们。 Ideally, the different Foo derivatives would somehow "self-register", but listing them all in one central place is also acceptable. 理想情况下,不同的Foo衍生物将以某种方式“自我注册”,但将它们全部集中在一个中心位置也是可以接受的。
Edit: 编辑:
Some clarifications based on comments / answers: 基于评论/答案的一些说明:
Foo
/ BarInterface
, ie they don't know about the concrete derived classes. 在我的情况下,有必要使用(类似)字符串来调用工厂,因为工厂的调用者使用Foo
/ BarInterface
多态性,即他们不了解具体的派生类。 On the other hand in Bar we want to use template methods of the derived Foo classes and facilitate inlining, that's why we really need the templated derived Bar
classes (rather than accessing Foo objects through some base-class interface). 另一方面,在Bar中,我们希望使用派生的Foo类的模板方法并简化内联,这就是我们真正需要模板化的派生Bar
类的原因(而不是通过某些基类接口访问Foo对象)。 BarInterface
and Bar
. 但是,他们不知道Bar的存在,实际上我们有多个不同的类,例如BarInterface
和Bar
。 So we cannot create "constructor objects" of Bar and save them in a map the same way we can do it for a foo_factory
. 因此,我们无法创建Bar的“构造器对象”,也无法像对foo_factory
一样将其保存在地图中。 What I think is needed is some kind of "compile-time map" (or list) of all the derived Foo types, such that when defining the bar_factory, the compiler can iterate over them, but I don't know how to do that... 我认为需要的是所有派生的Foo类型的某种“编译时映射”(或列表),以便在定义bar_factory时,编译器可以对其进行迭代,但是我不知道该怎么做。 ... Edit2: 编辑2:
Additional constraints that proofed to be relevant during discussion : 在讨论过程中被证明是相关的其他限制条件:
SpecificFoo<double>::name
is always valid. 特别是SpecificFoo<double>::name
始终有效。 @Julius' answer has been extended to facilitate this already. @Julius的答案已被扩展以方便此操作。 For @Yakk's the same can probably be done (but it will take me some time for figure it out in detail). 对于@Yakk来说,可以完成相同的操作(但需要花费一些时间来详细了解它)。 dynamic_cast
to the corresponding concrete derived Foo). 它还传递一些参数并进行某种类型转换(特别是,它可能具有Foo引用,应将其动态dynamic_cast
为对应的具体派生的Foo)。 Therefore a solution that allows to write this code inline during definition of the bar_factory seems most readable to me. 因此,一种解决方案允许在定义bar_factory的过程中内联编写此代码,这对我来说似乎最具可读性。 @Julius' answer works great here, even if the loop code with tuples is a little verbose. @Julius的答案在这里非常有用,即使使用元组的循环代码有些冗长。 types
template or with tuples), which is already great. 有两个答案可以在一个中心位置(使用types
模板或元组)定义Foo类型(或模板)的列表,这已经很好了。 However, for other reasons I already have in the same central place a list of macro calls, one for each foo, like DECLARE_FOO(FooA, "A") DECLARE_FOO(FooB, "B") ...
. 但是,由于其他原因,我已经在同一中央位置有一个宏调用列表,每个foo调用一个宏调用,如DECLARE_FOO(FooA, "A") DECLARE_FOO(FooB, "B") ...
Can the declaration of FooTypes
be somehow take advantage of that, so I don't have to list them again? 可以以某种方式利用FooTypes
的声明,因此我不必再次列出它们? I guess such type lists cannot be declared iteratively (appending to an already existing list), or can it? 我猜想这样的类型列表不能被迭代声明(追加到已经存在的列表中),或者可以吗? In the absence of that, probably with some macro magic it would be possible. 在这种情况下,也许可以使用一些宏观魔术。 Maybe always redefining and thus appending to a preprocessor list in the DECLARE_FOO
calls, and then finally some "iterate over loop" to define the FooTypes
type list. 也许总是在DECLARE_FOO
调用中重新定义并附加到预处理器列表,然后最终进行一些“迭代循环”来定义FooTypes
类型列表。 IIRC boost preprocessor has facilities to loop over lists (although I don't want a boost dependency). IIRC boost预处理器具有循环遍历列表的功能(尽管我不希望boost依赖)。 For some more context
, you can think of the different Foo and it's template argument as classes similar to Eigen::Matrix<Scalar>
and the Bar are cost functors to be used with Ceres. 对于更多context
,您可以将不同的Foo和它的模板参数视为类似于Eigen::Matrix<Scalar>
,而Bar是与Ceres一起使用的成本函数。 The bar factory returns objects like ceres::AutoDiffCostFunction<CostFunctor<SpecificFoo>, ...>
as ceres::CostFunction*
pointers. 条形工厂将返回诸如ceres::AutoDiffCostFunction<CostFunctor<SpecificFoo>, ...>
作为ceres::CostFunction*
指针。
Edit3: 编辑3:
Based on @Julius' answer I created a solution that works with Bars that are templates as well as template templates. 基于@Julius的答案,我创建了一个可与作为模板以及模板模板的Bars一起使用的解决方案。 I suspect one could unify bar_tmpl_factory
and bar_ttmpl_factory
into one function using variadic variadic template templates (is that a thing?). 我怀疑可以使用可变参数模板模板将bar_tmpl_factory
和bar_ttmpl_factory
统一为一个函数(是这样吗?)。
TODO: 去做:
bar_tmpl_factory
and bar_ttmpl_factory
bar_tmpl_factory
和bar_ttmpl_factory
Making the "single place" listing the Foos even simpler
from aboveMaking the "single place" listing the Foos even simpler
从上方Making the "single place" listing the Foos even simpler
types
template (but in a way such that the creator function can be defined inline at the call site of the loop over all foo types).types
模板替换元组的使用(但是可以在循环的调用站点上对所有foo类型的内联定义creator函数的方式)。I consider the question answered and if anything the above points should be separate questions. 我认为这个问题已经回答,以上几点应该是单独的问题。
template<class...Ts>struct types_t {};
template<class...Ts>constexpr types_t<Ts...> types{};
that lets us work with bundles of types without the overhead of a tuple. 这使我们可以处理类型捆绑包,而没有元组的开销。
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{};
this lets us work with types as values. 这使我们可以将类型用作值。
Now a type tag map is a function that takes a type tag, and returns another type 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 > >;
}
};
this takes a template type map and makes it into a tag map. 这需要一个模板类型映射,并将其制作为标签映射。
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)... );
}
that lets us test a condition on a bunch of types, and run an operation on the one that "succeeds". 这样我们就可以测试一堆类型的条件,并对“成功”的类型运行操作。
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>...
);
}
};
now we want to make shared pointers of some template class. 现在我们要为某些模板类创建共享指针。
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)...); };
}
};
so that makes shared pointers given a type. 这样就使共享指针具有一种类型。
template<class Second, class First>
struct compose {
template<class...Args>
constexpr decltype(auto) operator()(Args&&...args) const {
return Second{}(First{}( std::forward<Args>(args)... ));
}
};
now we can compose function objects at compile time. 现在我们可以在编译时编写函数对象。
Next wire it up. 接下来将其连接起来。
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;
The original design was actually c++20 with lambdas instead of those struct
s for shared_ptr_maker
and the like. 最初的设计实际上是带有lambda的c ++ 20 ,而不是shared_ptr_maker
之类的struct
。
Both make_foos
and make_bars
have zero runtime state. make_foos
和make_bars
运行时状态make_bars
零。
Write a generic factory like the following that allows registration at the class site: 编写如下所示的通用工厂,以允许在课程站点上注册:
template <typename Base>
class Factory {
public:
template <typename T>
static bool Register(const char * name) {
get_mapping()[name] = [] { return std::make_unique<T>(); };
return true;
}
static std::unique_ptr<Base> factory(const std::string & name) {
auto it = get_mapping().find(name);
if (it == get_mapping().end())
return {};
else
return it->second();
}
private:
static std::map<std::string, std::function<std::unique_ptr<Base>()>> & get_mapping() {
static std::map<std::string, std::function<std::unique_ptr<Base>()>> mapping;
return mapping;
}
};
And then use it like: 然后像这样使用它:
struct FooA: Foo {
static constexpr char const* name = "A";
inline static const bool is_registered = Factory<Foo>::Register<FooA>(name);
inline static const bool is_registered_bar = Factory<BarInterface>::Register<Bar<FooA>>(name);
void hello() override { std::cout << "Hello " << name << std::endl; }
};
and 和
std::unique_ptr<Foo> foo_factory(const std::string& name) {
return Factory<Foo>::factory(name);
}
Note: there is no way to guarantee that the class would be registered. 注意:无法保证将注册该类。 The compiler might decide not to include the translation unit, if there are no other dependencies. 如果没有其他依赖关系,则编译器可能会决定不包括转换单元。 It is probably better to simply register all classes in one central place. 最好只在一个中心位置注册所有课程。 Also note that the self-registering implementation depends on inline variables (C++17). 还要注意,自注册实现取决于内联变量(C ++ 17)。 It is not a strong dependence, and it is possible to get rid of it by declaring the booleans in the header and defining them in the CPP (which makes self-registering uglier and more prone to failing to register). 这不是一个很强的依赖关系,可以通过在标头中声明布尔值并在CPP中定义它们来摆脱它(这使自注册更加丑陋,并且更容易失败注册)。
The examples above assume that the definition of Bar<T>
is moved above Foo
. 上面的示例假定Bar<T>
的定义移到Foo
上方。 If that is impossible, then the registration can be done in an initialization function, in a cpp: 如果这不可能,那么可以在cpp中的初始化函数中完成注册:
// If possible, put at the header file and uncomment: // inline const bool barInterfaceInitialized = [] { Factory<Foo>::Register<FooA>(FooA::name); Factory<Foo>::Register<FooB>(FooB::name); Factory<Foo>::Register<FooC>(FooC::name); Factory<BarInterface>::Register<Bar<FooA>>(FooA::name); Factory<BarInterface>::Register<Bar<FooB>>(FooB::name); Factory<BarInterface>::Register<Bar<FooC>>(FooC::name); return true; }();
In C++17, we can apply the fold expression to simplify the storing process of generating functions std::make_unique<FooA>()
, std::make_unique<FooB>()
, and so on into the factory class in this case. 在C ++ 17中,在这种情况下,我们可以应用fold表达式来简化生成函数std::make_unique<FooA>()
, std::make_unique<FooB>()
的存储过程。 。
To begin with, for convenience, let us define the following type alias Generator
which describes the type of each generating function [](){ return std::make_unique<T>(); }
首先,为方便起见,让我们定义以下类型别名Generator
,它描述每个生成函数的类型[](){ return std::make_unique<T>(); }
[](){ return std::make_unique<T>(); }
: [](){ return std::make_unique<T>(); }
:
template<typename T>
using Generator = std::function<std::unique_ptr<T>(void)>;
Next, we define the following rather generic functor createFactory
which returns each factory as a hash map std::unordered_map
. 接下来,我们定义以下相当通用的函子createFactory
,该函数将每个工厂作为哈希映射std::unordered_map
。 Here I apply the fold expression with the comma operators. 在这里,我将fold表达式与逗号运算符一起应用。 For instance, createFactory<BarInterface, Bar, std::tuple<FooA, FooB, FooC>>()()
returns the hash map corresponding to your function bar_factory
: 例如, createFactory<BarInterface, Bar, std::tuple<FooA, FooB, FooC>>()()
返回与您的函数bar_factory
相对应的哈希图:
template<typename BaseI, template<typename> typename I, typename T>
void inserter(std::unordered_map<std::string_view, Generator<BaseI>>& map)
{
map.emplace(T::name, [](){ return std::make_unique<I<T>>(); });
}
template<typename BaseI, template<typename> class I, typename T>
struct createFactory {};
template<typename BaseI, template<typename> class I, typename... Ts>
struct createFactory<BaseI, I, std::tuple<Ts...>>
{
auto operator()()
{
std::unordered_map<std::string_view, Generator<BaseI>> map;
(inserter<BaseI, I, Ts>(map), ...);
return map;
}
};
This functor enables us to list FooA, FooB, FooC, ...
all in one central place as follows: 此函子使我们能够在一个中心位置列出FooA, FooB, FooC, ...
,如下所示:
DEMO (where I added virtusl destructors in base classes) DEMO (我在基类中添加了virtusl析构函数)
template<typename T>
using NonInterface = T;
// This can be written in one central place.
using FooTypes = std::tuple<FooA, FooB, FooC>;
int main()
{
const auto foo_factory = createFactory<Foo, NonInterface, FooTypes>()();
const auto foo = foo_factory.find("A");
if(foo != foo_factory.cend()){
foo->second()->hello();
}
const auto bar_factory = createFactory<BarInterface, Bar, FooTypes>()();
const auto bar = bar_factory.find("C");
if(bar != bar_factory.cend()){
bar->second()->world();
}
return 0;
}
What I think is needed is some kind of "compile-time map" (or list) of all the derived Foo types, such that when defining the bar_factory, the compiler can iterate over them, but I don't know how to do that... 我认为需要的是所有派生的Foo类型的某种“编译时映射”(或列表),以便在定义bar_factory时,编译器可以对其进行迭代,但是我不知道该怎么做。 ...
Here is one basic option: 这是一个基本选项:
#include <cassert>
#include <tuple>
#include <utility>
#include "foo_and_bar_without_factories.hpp"
////////////////////////////////////////////////////////////////////////////////
template<std::size_t... indices, class LoopBody>
void loop_impl(std::index_sequence<indices...>, LoopBody&& loop_body) {
(loop_body(std::integral_constant<std::size_t, indices>{}), ...);
}
template<std::size_t N, class LoopBody>
void loop(LoopBody&& loop_body) {
loop_impl(std::make_index_sequence<N>{}, std::forward<LoopBody>(loop_body));
}
////////////////////////////////////////////////////////////////////////////////
using FooTypes = std::tuple<FooA, FooB, FooC>;// single registration
std::unique_ptr<Foo> foo_factory(const std::string& name) {
std::unique_ptr<Foo> ret{};
constexpr std::size_t foo_count = std::tuple_size<FooTypes>{};
loop<foo_count>([&] (auto i) {// `i` is an std::integral_constant
using SpecificFoo = std::tuple_element_t<i, FooTypes>;
if(name == SpecificFoo::name) {
assert(!ret && "TODO: check for unique names at compile time?");
ret = std::make_unique<SpecificFoo>();
}
});
return ret;
}
std::unique_ptr<BarInterface> bar_factory(const std::string& name) {
std::unique_ptr<BarInterface> ret{};
constexpr std::size_t foo_count = std::tuple_size<FooTypes>{};
loop<foo_count>([&] (auto i) {// `i` is an std::integral_constant
using SpecificFoo = std::tuple_element_t<i, FooTypes>;
if(name == SpecificFoo::name) {
assert(!ret && "TODO: check for unique names at compile time?");
ret = std::make_unique< Bar<SpecificFoo> >();
}
});
return ret;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.