我在尝试建立工厂功能时遇到了一个问题, 给定一个ID和一个类型,它将返回正确的(模板化)子类。
这是要解决的问题
id()
值一建立连接就通过网络发送,并向接收者指定如何对字节序列进行编码。接收者预先知道它期望的类型T,但是直到它获得该值之前,不知道该类型T如何在线路上编码。它还指定返回时如何整理返回值(某种类型的U,其中U可能与T的类型相同或不同)。通常使用此代码,即有多个使用/期望不同类型的发送者/接收者;但是,给定发送方/接收方对之间使用的类型始终是固定的。
问题的基本示意图:我们有一个(简化的)基类,它定义了id()
template <typename T>
class foo
{
public:
virtual ~foo() { }
// Other methods
// This must return the same value for every type T
virtual std::uint8_t id() const noexcept = 0;
};
从那里开始,我们有一些子类:
template <typename T>
class bar : public foo<T>
{
public:
std::uint8_t id() const noexcept override { return 1; }
};
template <typename T>
class quux : public foo<T>
{
public:
std::uint8_t id() const noexcept override { return 2; }
};
对于实际的工厂功能,我需要存储一些 删除类型(例如bar,quux),以便我可以存储实际 均质容器中的创建函数。 实际上,我想要的语义大致等同于:
struct creation_holder
{
// Obviously this cannot work, as we cannot have virtual template functions
template <typename T>
virtual foo<T>* build() const;
};
template <template <typename> class F>
struct create : public creation_holder
{
// As above
template <typename T>
foo<T>* build() const override
{
return new F<T>();
}
};
std::unordered_map<std::uint8_t, create*>& mapping()
{
static std::unordered_map<std::uint8_t, create*> m;
return m;
}
template <typename T, template <typename> class F>
bool register_foo(F<T> foo_subclass,
typename std::enable_if<std::is_base_of<foo<T>, F<T>>::value>::type* = 0)
{
auto& m = mapping();
const auto id = foo_subclass.id();
creation_holder* hold = new create<F>();
// insert into map if it's not already present
}
template <typename T>
foo<T>* from_id(std::uint8_t id)
{
const auto& m = mapping();
auto it = m.find(id);
if(it == m.end()) { return nullptr; }
auto c = it->second;
return c->build<T>();
}
我尝试了很多方法来尝试获得类似的东西 语义,但没有运气。有没有办法做到这一点(我不在乎 实施方式有很大不同)。
答案 0 :(得分:3)
一些用于传递类型和类型束的实用程序类型:
template<class...Ts>
struct types_t {};
template<class...Ts>
constexpr types_t<Ts...> types{}; // C++14. In C++11, replace types<T> with types_t<T>{}. Then again, I don't use it.
template<class T>
struct tag_t {};
template<class T>
constexpr tag_t<T> tag{}; // C++14. In C++11, replace tag<T> with tag_t<T>{} below
现在我们写一个多工厂。
这里是ifactory
:
template<template<class...>class Z, class T>
struct ifactory {
virtual std::unique_ptr<Z<T>> tagged_build(tag_t<T>) const = 0;
virtual ~ifactory() {}
};
您传入要构建的标签,然后得到一个对象。很简单。
然后我们将它们捆绑在一起(在c++17 1 中会更容易,但是您要求的是c++11):
template<template<class...>class Z, class Types>
struct poly_ifactory_impl;
一种情况:
template<template<class...>class Z, class T>
struct poly_ifactory_impl<Z,types_t<T>>:
ifactory<Z, T>
{
using ifactory<Z, T>::tagged_build;
};
2种以上情况:
template<template<class...>class Z, class T0, class T1, class...Ts>
struct poly_ifactory_impl<Z,types_t<T0, T1, Ts...>>:
ifactory<Z, T0>,
poly_ifactory_impl<Z, types_t<T1, Ts...>>
{
using ifactory<Z, T0>::tagged_build;
using poly_ifactory_impl<Z, types_t<T1, Ts...>>::tagged_build;
};
我们将tagged_build
方法导入到派生类中。这意味着派生最多的poly_ifactory_impl
在相同的重载集中拥有所有tagged_build
方法。我们将使用它来分派给他们。
然后我们将其包装得很漂亮:
template<template<class...>class Z, class Types>
struct poly_ifactory:
poly_ifactory_impl<Z, Types>
{
template<class T>
std::unique_ptr<Z<T>> build() const {
return this->tagged_build(tag<T>);
}
};
注意,我要返回unique_ptr
;从工厂方法中恢复原始T*
就是代码异味。
拥有poly_ifactory<?>
的人只会做->build<T>()
而忽略tagged_
重载(除非他们想要它们;我让他们暴露在外)。每个tagged_build
是虚拟的,但build<T>
不是虚拟的。这就是我们模拟虚拟模板函数的方式。
这处理接口。另一方面,我们不需要手动实现每个build(tag_t<T>)
。我们可以使用CRTP来解决这个问题。
template<class D, class Base, template<class...>class Z, class T>
struct factory_impl : Base {
virtual std::unique_ptr<Z<T>> tagged_build( tag_t<T> ) const override final {
return static_cast<D const*>(this)->build_impl( tag<T> );
}
using Base::build;
};
template<class D, class Base, template<class...>class Z, class Types>
struct poly_factory_impl;
1种情况:
template<class D, class Base, template<class...>class Z, class T0>
struct poly_factory_impl<D, Base, Z, types_t<T0>> :
factory_impl<D, Base, Z, T0>
{
using factory_impl<D, Base, Z, T0>::tagged_build;
};
2+类型的情况:
template<class D, class Base, template<class...>class Z, class T0, class T1, class...Ts>
struct poly_factory_impl<D, Base, Z, types_t<T0, T1, Ts...>> :
factory_impl<D, poly_factory_impl<D, Base, Z, types_t<T1, Ts...>>, Z, T0>
{
using factory_impl<D, poly_factory_impl<D, Base, Z, types_t<T1, Ts...>>, Z, T0>::tagged_build;
};
这是编写tagged_build(tag_t<T>)
方法的一系列ifactory
重载,并将它们重定向到D::build_impl(tag_t<T>)
,其中D
是理论派生类型。
为了避免必须使用虚拟继承,存在了花哨的“ pass Basearound”。我们线性继承,每个步骤实现一个tagged_build(tag<T>)
重载。他们全都使用CRTP非虚拟地向下调度。
使用方式如下:
struct bar {};
using my_types = types_t<int, double, bar>;
template<class T>
using vec = std::vector<T>;
using my_ifactory = poly_ifactory< vec, my_types >;
struct my_factory :
poly_factory_impl< my_factory, my_ifactory, vec, my_types >
{
template<class T>
std::unique_ptr< vec<T> > build_impl( tag_t<T> ) const {
return std::make_unique< std::vector<T> >( sizeof(T) );
// above is C++14; in C++11, use:
// return std::unique_ptr<vec<T>>( new vec<T>(sizeof(T)) );
}
};
并且my_factory
的实例满足my_ifactory
接口。
在这种情况下,我们为T
的向量创建一个唯一的ptr,其元素数等于sizeof(T)
。只是玩具而已。
伪代码设计。
该界面有一个
template<class T> R build
功能。派遣到
virtual R tagged_build(tag_t<T>) = 0;
方法。
有问题的T
从types_t<Ts...>
列表中提取。仅支持这些类型。
在实现方面,我们创建CRTP帮助器的线性继承。每个继承自最后一个,并覆盖virtual R tagged_build(tag_t<T>)
。
tagged_build
的实现使用CRTP将this
指针转换为派生自更高级别的类,并在其上调用build_impl(tag<T>)
。这是非运行时多态的一个示例。
因此,呼叫从build<T>
到virtual tagged_build(tag_t<T>)
到build_impl(tag<T>)
。用户只需与一个模板进行交互;实现者仅实现一个模板。中间的胶水virtual tagged_build
是从types_t
类型列表中生成的。
这是大约100行的“胶水”或帮助程序代码,作为交换,我们获得了有效的虚拟模板方法。
1 变为:
template<template<class...>class Z, class...Ts>
struct poly_ifactory_impl<Z,types_t<Ts...>>:
ifactory<Z, Ts>...
{
using ifactory<Z, Ts>::tagged_build...;
};
这更加简单明了。
最后,您可以像这样含糊其词地执行某项操作,而无需中央类型列表。如果您知道呼叫者和被叫者都知道类型,则可以将typeid
或typeindex
传递到ifactory
中,并通过虚拟调度派发void*
或类似的东西机制,并强制转换/检查是否为空/在映射中查找类型。
内部实现看起来与此类似,但是您不必将types_t
作为正式(或二进制)接口的一部分发布。
在外部,您将不得不“仅知道”支持哪些类型。在运行时,如果传入不受支持的类型,您可能会得到一个空的智能(或哑巴,ick)指针。
稍加注意,您甚至可以同时进行。公开一种有效,安全的机制,以将编译时已知的类型应用于模板。还公开一个基于“ try”的接口,该接口既使用高效的编译时已知系统(如果类型匹配),又退回到已检查的低效率运行时。您之所以这样做可能是出于深奥的向后二进制兼容性的原因(因此新软件可以通过过时的接口连接到新的或旧的API实现,并动态地处理旧的API实现)。
但是到那时,您是否考虑过使用COM?