我想为一组派生类实现通用工厂机制,该机制使我不仅可以通用实现工厂函数来创建该类的对象,还可以通用实现其他模板类的创建者,这些模板类中的一个作为模板参数派生类。
理想情况下,解决方案将仅使用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();
}
我正在寻找一种机制,使我可以在不列出所有类的情况下实现foo_factory
和bar_factory
,这样一旦添加例如{{1} }作为其他派生类。理想情况下,不同的Foo衍生物会以某种方式“自我注册”,但将它们全部集中在一个中心位置也是可以接受的。
编辑:
基于评论/答案的一些说明:
FooD
/ Foo
来实现多态,即他们不知道具体派生类。另一方面,在Bar中,我们希望使用派生的Foo类的模板方法并促进内联,这就是为什么我们确实需要模板化的派生BarInterface
类(而不是通过某些基类接口访问Foo对象)的原因。 / li>
Bar
和BarInterface
。因此,我们无法创建Bar的“构造器对象”并将其保存在地图中,就像对Bar
一样。我认为需要的是所有派生的Foo类型的某种“编译时映射”(或列表),以便在定义bar_factory时,编译器可以对其进行迭代,但是我不知道该怎么做。 ... 编辑2:
证明与during discussion相关的其他约束:
foo_factory
始终有效。 @Julius的答案已被扩展以方便此操作。对于@Yakk来说,可以完成相同的操作(但需要花费一些时间来详细了解它)。SpecificFoo<double>::name
)。因此,一种解决方案允许在定义bar_factory的过程中内联编写此代码,这对我来说似乎最具可读性。即使带有元组的循环代码有点冗长,@ Julius的回答在这里也很好用。dynamic_cast
模板或使用元组)定义Foo类型(或模板)的列表,这已经很好了。但是,由于其他原因,我已经在同一中央位置列出了一个宏调用列表,每个foo调用一个,例如types
。可以以某种方式利用DECLARE_FOO(FooA, "A") DECLARE_FOO(FooB, "B") ...
的声明,因此我不必再次列出它们?我猜想这样的类型列表不能被迭代声明(追加到已经存在的列表中),或者可以吗?在这种情况下,也许可以使用一些宏观魔术。也许总是在FooTypes
调用中重新定义并附加到预处理器列表,然后最终进行一些“迭代结束循环”以定义DECLARE_FOO
类型列表。 IIRC boost预处理器具有循环列表的功能(尽管我不希望boost依赖)。对于更多FooTypes
,您可以想到不同的Foo及其模板参数,其类似于context
的类,而Bar是Ceres的成本仿函数。条形工厂返回Eigen::Matrix<Scalar>
之类的对象作为ceres::AutoDiffCostFunction<CostFunctor<SpecificFoo>, ...>
指针。
Edit3:
基于@Julius的答案,我创建了一个可与作为模板以及模板模板的Bars一起使用的解决方案。我怀疑可以使用可变参数模板模板将ceres::CostFunction*
和bar_tmpl_factory
统一为一个函数(是这样吗?)。
待办事项:
bar_ttmpl_factory
和bar_tmpl_factory
bar_ttmpl_factory
点Making the "single place" listing the Foos even simpler
模板代替了元组的使用(但是以一种方式可以在循环的所有foo类型的调用站点上内联地定义creator函数)。我认为问题已得到回答,以上几点应单独回答。
答案 0 :(得分:3)
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;
和Done。
最初的设计实际上是c++20,带有lambda,而不是struct
和shared_ptr_maker
等。
make_foos
和make_bars
的运行时状态均为零。
答案 1 :(得分:2)
编写如下所示的通用工厂,以允许在课程站点上注册:
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;
}
};
然后像这样使用它:
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);
}
注意:没有办法保证可以注册该课程。如果没有其他依赖关系,则编译器可能会决定不包括转换单元。最好只在一个中心位置注册所有课程。还要注意,自注册实现取决于内联变量(C ++ 17)。这不是一个很强的依赖关系,可以通过在标头中声明布尔值并在CPP中定义它们来摆脱它(这使自注册更加丑陋,并且更容易失败注册)。
以上示例假定Bar<T>
的定义移至Foo
上方。如果那不可能,那么可以在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;
}();
答案 2 :(得分:1)
在C ++ 17中,在这种情况下,我们可以应用fold表达式来简化生成函数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)>;
接下来,我们定义以下相当通用的函子createFactory
,该函子将每个工厂作为哈希映射std::unordered_map
返回。
在这里,我将fold表达式与逗号运算符一起应用。
例如,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;
}
};
此函子使我们可以在一个中央位置列出FooA, FooB, FooC, ...
,如下所示:
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;
}
答案 3 :(得分:1)
我认为需要的是某种形式的“编译时映射”(或列表) 所有派生的Foo类型,以便在定义bar_factory时, 编译器可以遍历它们,但是我不知道该怎么做...
这是一个基本选项:
#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;
}