C ++ 17中的泛型工厂机制 [英] Generic factory mechanism in C++17

查看:94
本文介绍了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.

理想情况下,解决方案将仅使用C ++ 17功能(无依赖项).

Ideally a solution would only use C++17 features (no dependencies).

考虑此示例

#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_factorybar_factory而不列出所有类,这样,一旦添加例如FooD作为附加派生类,就无需更新它们.理想情况下,不同的Foo衍生物将以某种方式自我注册",但将它们全部集中在一个中心位置也是可以接受的.

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. Ideally, the different Foo derivatives would somehow "self-register", but listing them all in one central place is also acceptable.

基于评论/答案的一些说明:

Some clarifications based on comments / answers:

  • 在我的情况下,有必要使用(类似)字符串来调用工厂,因为工厂的调用者使用Foo/BarInterface的多态性,即它们不了解具体的派生类.另一方面,在Bar中,我们希望使用派生的Foo类的模板方法并促进内联,这就是为什么我们确实需要模板化的派生的Bar类(而不是通过某些基类接口访问Foo对象)的原因.
  • 我们可以假定所有派生的Foo类都在一个位置定义(因此,如果需要,可以在同一位置将它们全部列出一次的手动注册是可以接受的).但是,他们不知道Bar的存在,实际上我们有多个不同的类,例如BarInterfaceBar.因此,我们无法创建Bar的构造器对象",也无法像对foo_factory那样将其保存在地图中.我认为需要的是所有派生的Foo类型的某种编译时映射"(或列表),以便在定义bar_factory时,编译器可以对其进行迭代,但是我不知道该怎么做. ...
  • 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, i.e. they don't know about the concrete derived classes. 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).
  • 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). However, they do not know about the existence of Bar, and in fact we have multiple different classes like BarInterface and 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. 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...

在讨论过程中被证明具有相关性的其他约束

Additional constraints that proofed to be relevant during discussion:

  • 模板和模板模板:Foo实际上是模板(具有单个类参数),而Bar是使用具体Foo作为模板参数的模板模板. Foo模板没有专长,并且都具有相同的名称",因此查询任何具体类型都是可以的.特别是SpecificFoo<double>::name始终有效. @Julius的答案已得到扩展,以方便执行此操作.对于@Yakk来说,可以完成相同的操作(但需要花费一些时间来详细了解它).
  • 灵活的bar工厂代码: Bar的工厂所做的不仅仅是调用构造函数.它还传递一些参数并进行某种类型转换(尤其是,它可能具有Foo引用,该引用应dynamic_cast对应于对应的具体派生的Foo).因此,一种解决方案允许在bar_factory的定义期间内联编写此代码,这对我来说似乎最容易理解.即使带有元组的循环代码有点冗长,@ Julius的回答在这里也能很好地发挥作用.
  • 使"Foos"的单个位置"更简单:到目前为止,我认为对我而言,方法是拥有foo类型的编译时列表和迭代方法在他们之上.有两个答案可以在一个中心位置(使用types模板或使用元组)定义Foo类型(或模板)的列表,这已经很好了.但是,由于其他原因,我已经在同一中央位置有了一个宏调用列表,每个foo调用一个宏调用,例如DECLARE_FOO(FooA, "A") DECLARE_FOO(FooB, "B") ....可以以某种方式利用FooTypes的声明,因此我不必再次列出它们?我猜想这样的类型列表不能被迭代声明(追加到已经存在的列表中),或者可以吗?在这种情况下,可能会有一些宏观魔术效果是可能的.也许总是在DECLARE_FOO调用中重新定义并附加到预处理器列表,然后最终进行一些迭代结束循环"以定义FooTypes类型列表. IIRC Boost预处理器具有循环列表的功能(尽管我不希望Boost依赖).
  • 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. The Foo templates have no specializations and all have the same "name", so querying any concrete type is fine. In particular SpecificFoo<double>::name is always valid. @Julius' answer has been extended to facilitate this already. For @Yakk's the same can probably be done (but it will take me some time for figure it out in detail).
  • Flexible bar factory code: The factory for Bar does a little more than just call the constructor. 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). Therefore a solution that allows to write this code inline during definition of the bar_factory seems most readable to me. @Julius' answer works great here, even if the loop code with tuples is a little verbose.
  • 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. 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. 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") .... Can the declaration of FooTypes be somehow take advantage of that, so I don't have to list them again? 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. IIRC boost preprocessor has facilities to loop over lists (although I don't want a boost dependency).

对于更多的context,您可以想到不同的Foo及其模板参数,因为它们类似于Eigen::Matrix<Scalar>的类,而Bar是Ceres的成本仿函数. bar工厂将像ceres::AutoDiffCostFunction<CostFunctor<SpecificFoo>, ...>这样的对象作为ceres::CostFunction*指针返回.

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. The bar factory returns objects like ceres::AutoDiffCostFunction<CostFunctor<SpecificFoo>, ...> as ceres::CostFunction* pointers.

Edit3:

基于@Julius的答案,我创建了一个可与作为模板以及模板模板的Bars一起使用的解决方案.我怀疑可以使用可变参数模板模板将bar_tmpl_factorybar_ttmpl_factory统一为一个函数(是这样吗?).

Based on @Julius' answer I created a solution that works with Bars that are templates as well as template templates. 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
  • 从上方的点Making the "single place" listing the Foos even simpler
  • 可能用@Yakk的types模板代替了元组的使用(但是可以在循环的调用站点上对所有foo类型的内联定义creator函数).
  • combine bar_tmpl_factory and bar_ttmpl_factory
  • the point Making the "single place" listing the Foos even simpler from above
  • 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).

我认为已回答的问题,以上几点应视为单独的问题.

推荐答案

我认为需要的是某种形式的编译时映射"(或列表) 所有派生的Foo类型,例如,在定义bar_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...

这是一个基本选项:

#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;
}

这篇关于C ++ 17中的泛型工厂机制的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆