[英]Generic factory mechanism in C++17
我想為一組派生類實現一種通用工廠機制,該機制使我不僅可以通用地實現一個工廠函數來創建該類的對象,而且還可以實現其他模板類的創建者,這些其他模板類的創建者將其中一個派生類作為模板參數。
理想情況下,解決方案僅使用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
,這樣,一旦我將FooD
添加為其他派生類,就不必更新它們。 理想情況下,不同的Foo衍生物將以某種方式“自我注冊”,但將它們全部集中在一個中心位置也是可以接受的。
編輯:
基於評論/答案的一些說明:
Foo
/ BarInterface
多態性,即他們不了解具體的派生類。 另一方面,在Bar中,我們希望使用派生的Foo類的模板方法並簡化內聯,這就是我們真正需要模板化的派生Bar
類的原因(而不是通過某些基類接口訪問Foo對象)。 BarInterface
和Bar
。 因此,我們無法創建Bar的“構造器對象”,也無法像對foo_factory
一樣將其保存在地圖中。 我認為需要的是所有派生的Foo類型的某種“編譯時映射”(或列表),以便在定義bar_factory時,編譯器可以對其進行迭代,但是我不知道該怎么做。 ... 編輯2:
在討論過程中被證明是相關的其他限制條件:
SpecificFoo<double>::name
始終有效。 @Julius的答案已被擴展以方便此操作。 對於@Yakk來說,可以完成相同的操作(但需要花費一些時間來詳細了解它)。 dynamic_cast
為對應的具體派生的Foo)。 因此,一種解決方案允許在定義bar_factory的過程中內聯編寫此代碼,這對我來說似乎最具可讀性。 @Julius的答案在這里非常有用,即使使用元組的循環代碼有些冗長。 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的答案,我創建了一個可與作為模板以及模板模板的Bars一起使用的解決方案。 我懷疑可以使用可變參數模板模板將bar_tmpl_factory
和bar_ttmpl_factory
統一為一個函數(是這樣嗎?)。
去做:
bar_tmpl_factory
和bar_ttmpl_factory
Making the "single place" listing the Foos even simpler
從上方Making the "single place" listing the Foos even simpler
types
模板替換元組的使用(但是可以在循環的調用站點上對所有foo類型的內聯定義creator函數的方式)。我認為這個問題已經回答,以上幾點應該是單獨的問題。
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;
和完成 。
最初的設計實際上是帶有lambda的c ++ 20 ,而不是shared_ptr_maker
之類的struct
。
make_foos
和make_bars
運行時狀態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;
}
};
然后像這樣使用它:
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; }();
在C ++ 17中,在這種情況下,我們可以應用fold表達式來簡化生成函數std::make_unique<FooA>()
, std::make_unique<FooB>()
的存儲過程。 。
首先,為方便起見,讓我們定義以下類型別名Generator
,它描述每個生成函數的類型[](){ return std::make_unique<T>(); }
[](){ 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;
}
我認為需要的是所有派生的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;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.