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

查看:31
本文介绍了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.所以我们不能像 foo_factory 那样创建 Bar 的构造器对象"并将它们保存在 map 中.我认为需要的是所有派生 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...

证明相关的其他约束讨论期间:

  • 模板和模板模板: Foo 实际上是模板(带有单个类参数),而 Bar 是模板模板,将具体的 Foo 作为模板参数.Foo 模板没有特化,并且都具有相同的名称",因此查询任何具体类型都可以.特别是 SpecificFoo<double>::name 始终有效.@Julius 的答案已经扩展以促进这一点.对于@Yakk,可能也可以这样做(但我需要一些时间来详细弄清楚).
  • 灵活的条形工厂代码: 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 的类,而 Bar 是成本函子与谷神星一起使用.酒吧工厂将 ceres::AutoDiffCostFunction, ...> 等对象作为 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.

根据@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
  • 要点让上面列出Foos的单一地方"更加简单
  • 也许可以用@Yakk 的 types 模板替换元组的使用(但在某种程度上,可以在循环的调用点内联地定义创建者函数对所有 foo 类型).
  • 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天全站免登陆