简体   繁体   English

C ++ 17中的通用工厂机制

[英]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();
}

run it 运行

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_factorybar_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: 基于评论/答案的一些说明:

  • It is necessary in my case to invoke the factories with (something like) a string, since the callers of the factories use polymorphism with 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对象)。
  • We can assume that all derived Foo classes are defined in one place (and a manual registration where we list them all once in the same place is therefore acceptable, if necessary). 我们可以假设所有派生的Foo类都在一个位置定义(因此,如果需要,可以在同一位置一次列出所有它们的手动注册是可以接受的)。 However, they do not know about the existence of Bar, and in fact we have multiple different classes like BarInterface and Bar . 但是,他们不知道Bar的存在,实际上我们有多个不同的类,例如BarInterfaceBar 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 : 在讨论过程中被证明是相关的其他限制条件:

  • Templates and template templates: The Foo are actually templates (with a single class argument) and the Bar are template templates taking a concrete Foo as template argument. 模板和模板模板: Foo实际上是模板(具有单个类参数),而Bar是采用具体Foo作为模板参数的模板模板。 The Foo templates have no specializations and all have the same "name", so querying any concrete type is fine. Foo模板没有专长,并且都具有相同的“名称”,因此可以查询任何具体类型。 In particular 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来说,可以完成相同的操作(但需要花费一些时间来详细了解它)。
  • Flexible bar factory code: The factory for Bar does a little more than just call the constructor. 灵活的Bar工厂代码: Bar的工厂所做的不仅仅是调用构造函数。 It also passes some arguments and does some type casting (in particular, it may have Foo references that should be 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的答案在这里非常有用,即使使用元组的循环代码有些冗长。
  • Making the "single place" listing the Foos even simpler: From the answers so far I believe the way to go for me is having a compile-time list of foo types and a way to iterate over them. 使列出Foos的“单个位置”更加简单:从到目前为止的答案中,我相信对我而言,要走的路是拥有foo类型的编译时列表以及对其进行迭代的方法。 There are two answers that define a list of Foo types (or templates) in one central place (either with a 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_factorybar_ttmpl_factory统一为一个函数(是这样吗?)。

run it 运行

TODO: 去做:

  • combine bar_tmpl_factory and bar_ttmpl_factory 结合bar_tmpl_factorybar_ttmpl_factory
  • the point Making the "single place" listing the Foos even simpler from above 要点Making the "single place" listing the Foos even simpler从上方Making the "single place" listing the Foos even simpler
  • maybe replacing the use of tuples with @Yakk's 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). 也许可以用@Yakk的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;

and Done . 完成

The original design was actually with lambdas instead of those struct s for shared_ptr_maker and the like. 最初的设计实际上是带有lambda的 ,而不是shared_ptr_maker之类的struct

Both make_foos and make_bars have zero runtime state. make_foosmake_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中定义它们来摆脱它(这使自注册更加丑陋,并且更容易失败注册)。

edit 编辑

  1. The disadvantage of this answer, when compared to others, is that it performs the registration during start-up and not during compilation. 与其他答案相比,此答案的缺点是它在启动期间而不是在编译期间执行注册。 On the other hand, this makes the code much simpler. 另一方面,这使代码更简单。
  2. 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM