gpt4 book ai didi

c++ - C++ 17中的泛型工厂机制

转载 作者:太空狗 更新时间:2023-10-29 20:08:18 26 4
gpt4 key购买 nike

我想为一组派生类实现一种通用工厂机制,该机制使我不仅可以通用地实现一个工厂函数来创建该类的对象,而且还可以实现其他模板类的创建者,这些其他模板类的创建者将其中一个派生类作为模板参数。

理想情况下,解决方案仅使用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类(而不是通过某些基类接口(interface)访问Foo对象)的原因。
  • 我们可以假定所有派生的Foo类都在一个位置定义(因此,如果需要,可以在同一位置将它们全部列出一次的手动注册是可以接受的)。但是,他们不知道Bar的存在,实际上我们有多个不同的类,例如BarInterfaceBar。因此,我们无法创建Bar的“构造器对象”,也无法像对foo_factory那样将其保存在 map 中。我认为需要的是所有派生的Foo类型的某种“编译时映射”(或列表),以便在定义bar_factory时,编译器可以对其进行迭代,但是我不知道该怎么做。 ...

  • 编辑2:

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

  • 对于更多 context,您可以想到不同的Foo及其模板参数,因为类似于 Eigen::Matrix<Scalar>的类和Bar是与Ceres一起使用的成本仿函数。条工厂返回诸如 ceres::AutoDiffCostFunction<CostFunctor<SpecificFoo>, ...>之类的对象作为 ceres::CostFunction*指针。

    编辑3:

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

    run it

    去做:
  • 结合bar_tmpl_factorybar_ttmpl_factory
  • 上方的点 Making the "single place" listing the Foos even simpler
  • 可能用@Yakk的types模板替换了元组的使用(但是以一种方式可以在循环的所有foo类型的调用站点上内联地定义creator函数)。

  • 我认为这个问题已经回答,以上几点应单独回答。

    最佳答案

    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++ - C++ 17中的泛型工厂机制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55045367/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com