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

问题描述 投票:3回答:4

我想为一组派生类实现一个通用的工厂机制,它允许我不仅一般地实现一个工厂函数来创建该类的对象,而且还创建其他模板类的创建者,这些模板类作为模板参数之一派生类。

理想情况下,解决方案只使用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();
}

run it

我正在寻找一种机制,允许我实现foo_factorybar_factory而不列出所有类,这样一旦我添加例如FooD作为额外的派生类,它们就不需要更新。理想情况下,不同的Foo衍生物会以某种方式“自我注册”,但将它们全部列在一个中心位置也是可以接受的。

编辑:

基于评论/答案的一些澄清:

  • 在我的情况下,有必要使用(类似)字符串调用工厂,因为工厂的调用者使用Foo / BarInterface的多态性,即他们不知道具体的派生类。另一方面,在Bar中,我们希望使用派生的Foo类的模板方法并促进内联,这就是为什么我们真的需要模板化派生的Bar类(而不是通过一些基类接口访问Foo对象)。
  • 我们可以假设所有派生的Foo类都在一个地方定义(如果需要,我们在同一个地方列出所有派生的Foo类都是可以接受的)。然而,他们不知道Bar的存在,事实上我们有多个不同的类,如BarInterfaceBar。所以我们不能创建Bar的“构造函数对象”并将它们保存在地图中,就像我们为foo_factory做的那样。我认为需要的是所有派生的Foo类型的某种“编译时映射”(或列表),这样在定义bar_factory时,编译器可以迭代它们,但我不知道如何做到这一点...

Aaditi:

证明与qazxsw poi相关的其他限制因素:

  • 模板和模板模板:Foo实际上是模板(具有单个类参数),而Bar是模板模板,将具体的Foo作为模板参数。 Foo模板没有专门化,并且都具有相同的“名称”,因此查询任何具体类型都可以。特别是during discussion总是有效的。 @Julius的答案已经扩展到已经为此提供了便利。对于@ Yakk来说,可能也是如此(但我需要一些时间来详细说明)。
  • 灵活的条形码工厂代码:Bar的工厂不仅仅是调用构造函数。它还传递一些参数并进行某种类型的转换(特别是,它可能有Foo引用,应该是SpecificFoo<double>::name到相应的具体派生的Foo)。因此,允许在定义bar_factory期间内联编写此代码的解决方案对我来说似乎最具可读性。 @Julius的回答在这里很有用,即使带有元组的循环代码有点冗长。
  • 使列出Foos的“单一位置”更简单:从目前为止的答案我相信我的方法是拥有foo类型的编译时列表以及迭代它们的方法。有两个答案可以在一个中心位置定义一个Foo类型(或模板)列表(使用dynamic_cast模板或使用元组),这已经非常好了。然而,由于其他原因,我已经在同一中心位置有一个宏调用列表,每个foo一个,如typesDECLARE_FOO(FooA, "A") DECLARE_FOO(FooB, "B") ...的声明能否以某种方式利用它,所以我不必再次列出它们?我想这样的类型列表不能迭代地声明(附加到已经存在的列表),或者它可以吗?如果没有这个,可能会有一些宏观魔法,这是可能的。也许总是重新定义并因此附加到FooTypes调用中的预处理器列表,然后最后一些“迭代循环”来定义DECLARE_FOO类型列表。 IIRC boost预处理器具有遍历列表的功能(尽管我不希望提升依赖性)。

对于更多的FooTypes,您可以将不同的Foo和它的模板参数视为类似于context的类,而Bar是与Ceres一起使用的成本函数。酒吧工厂将Eigen::Matrix<Scalar>等对象作为ceres::AutoDiffCostFunction<CostFunctor<SpecificFoo>, ...>指针返回。

Aadita:

基于@Julius的回答,我创建了一个与Bars一起使用的解决方案,它是模板和模板模板。我怀疑有人可以使用可变参数变量模板模板将q​​azxswpoi和ceres::CostFunction*统一到一个函数中(这是一个什么东西?)。

bar_tmpl_factory

ALL:

  • 结合bar_ttmpl_factoryrun it
  • 从上面点bar_tmpl_factory
  • 也许用@ Yakk的bar_ttmpl_factory模板替换元组的使用(但是在某种程度上可以在所有foo类型的循环调用站点内联定义创建者函数)。

我认为这个问题得到了回答,如果有的话,上述几点应该是单独的问题。

c++ templates polymorphism c++17 factory
4个回答
1
投票

我认为需要的是所有派生的Foo类型的某种“编译时映射”(或列表),这样在定义bar_factory时,编译器可以迭代它们,但我不知道如何做到这一点...

这是一个基本选项:

Making the "single place" listing the Foos even simpler

3
投票
types

这让我们可以使用捆绑类型而不需要元组的开销。

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

这让我们可以使用类型作为值。

现在,类型标记映射是一个带有类型标记的函数,并返回另一个类型标记。

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; 与lambdas而不是Done之类的那些s。

structshared_ptr_maker都没有运行时状态。


2
投票

编写如下的通用工厂,允许在类网站注册:

make_foos

然后使用它像:

make_bars

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

注意:无法保证该类将被注册。如果没有其他依赖项,编译器可能决定不包括转换单元。简单地在一个中心位置注册所有类可能更好。另请注意,自注册实现依赖于内联变量(C ++ 17)。它不是一种强烈的依赖性​​,并且可以通过在标题中声明布尔值并在CPP中定义它们来消除它(这使得自我注册更加丑陋并且更容易失败注册)。

edit

  1. 与其他人相比,这个答案的缺点是它在启动期间而不是在编译期间执行注册。另一方面,这使代码更简单。
  2. 上面的例子假设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; } }; 的定义在std::unique_ptr<Foo> foo_factory(const std::string& name) { return Factory<Foo>::factory(name); } 之上移动。如果这是不可能的,那么注册可以在初始化函数中完成,在cpp中: Bar<T>

1
投票

在C ++ 17中,我们可以应用fold表达式来简化在这种情况下生成函数Foo// 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; }(); 等的存储过程到工厂类中。


首先,为方便起见,让我们定义以下类型别名std::make_unique<FooA>(),它描述了每个生成函数std::make_unique<FooB>()的类型:

Generator

接下来,我们定义以下相当通用的仿函数[](){ return std::make_unique<T>(); },它将每个工厂作为哈希映射template<typename T> using Generator = std::function<std::unique_ptr<T>(void)>; 返回。在这里,我使用逗号运算符应用fold表达式。例如,createFactory返回与您的函数std::unordered_map对应的哈希映射:

createFactory<BarInterface, Bar, std::tuple<FooA, FooB, FooC>>()()

这个仿函数使我们能够在一个中心位置列出bar_factory,如下所示:

qazxsw point(我在基类中添加了虚拟析构函数)

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;
    }
};
© www.soinside.com 2019 - 2024. All rights reserved.