我真的不知道我的理想解决方案究竟是什么样的,所以请原谅任何模糊或不恰当的术语.
我正在开发一个项目,涉及将节点串行连接到管道以处理来自硬件控制器的数据.在用户输入时,将以数据流编程样式将值从节点推送到节点.每个节点都可以有不同的输入和输出类型,因此从int开始可能最终在管道末端产生用户定义的类型,或者根本没有类型(只是没有参数的函数调用).在通过管道传递数据方面,我选择将事物实现为一系列函子,因此在将数据从节点传递到节点时会有一定程度的类型擦除.每个Functor只处理它的参数并将它们交给下一个Functor.
问题是改变节点的布局或添加或减去它们.我很难理解如何保存这些不同类型的映射,并且仍然可以将它们链接在一起,而不必做一堆丑陋且昂贵的dynamic_cast if语句.它们都来自公共类模板,但它们的实际类型根据输入/输出参数而不同.我已经将接口拆分为输入和输出两半,因此接收节点可以存储指向其源节点类型的指针,而无需知道该节点的输入类型和之前的所有节点.
class IMapping
{
virtual void Enable() = 0;
virtual void Disable() = 0;
...
}
template <typename InputT>
class IMapping_Inlet
{
public:
virtual void SetSource(IMapping_Outlet<InputT> *pSource) { /* Stores pointer to new source */ };
virtual IFunctor<InputT> GetFunc() = 0; // IFunctor<T> is just an alias for std::function<void(T)> for now.
...
protected:
void SetFunc(IFunctor<InputT> *pFunc){ m_Func = pFunc; }; // Called from the derived class to set the function that processes InputT
IMapping_Outlet<InputT> *m_Source;
IFunctor<InputT> *m_Func;
}
template <typename OutputT>
class IMapping_Outlet
{
public:
virtual void SetTarget(IMapping_Inlet<OutputT> *pTarget) { /* Stores pointer and calls pTarget->GetFunc() */ };
virtual void SetFunc(IFunctor<OutputT> *pFunc) { /* Some mappings may want to swap out their functor */ };
...
protected:
IMapping_Inlet<OutputT> *m_Target;
IFunctor<OutputT> *m_TargetFunc;
}
template <typename InputT, OutputT>
class IMapping : public IMapping, public IMapping_Inlet<InputT>, public IMapping_Outlet<OutputT>
{
public:
virtual void Init() { /* Derived type creates a functor (probably pointing to one of its own member functions) and passes it to IMapping_Inlet::SetFunc() */ };
...
}
class MyDerivedMapping : public IMapping<int, bool>
{
void Init() override
{ // Haven't tested this yet
IMapping_Inlet<int>::SetFunc(
[auto tail = m_Target->GetFunc()] (int i) {
bool b = i > 10 ? true : false;
tail(b);
}
);
};
}
因此,假设您知道两个节点的类型,并且它们的输入/输出类型匹配,您可以轻松地将它们映射到一起.但是封闭的管道类不能将所有节点存储在泛型集合中,并且仍然能够根据具体类型将它们映射到一起.如果我们有虚拟模板函数会很好,因为我可以在IMapping接口中定义一个模板化的SetSource / SetTarget函数,并根据模板参数进行调度.
我想我可以根据输入和输出类型对Pipeline进行模板化,让映射节点在它们之间做任何他们喜欢的事情.管道应该只关心它的输入类型是否与第一个节点的输入类型匹配,并且其输出类型与最后一个节点的输出类型匹配.
我一直在阅读有关TMP,CRTP,SFINAE,协变返回类型等的内容,但大部分内容都是新材料而且在我脑海中.这感觉就像是在函数式编程领域,但我太缺乏经验,无法理解我是如何实际使用功能方法来弥补运行时和编译时间之间的差距.
作为我想要做的事情的一个例子:
设[T,U]表示IMapping< T,U>,即将T作为其输入并将U作为其输出的映射.
我有节点[int,bool] – > [bool,int]并希望将它们插入更长的节点串[int,int] – > [int,double] – > [double,double]之后第一个节点.
我希望能够从基类指针询问节点的输入或输出类型,并且如果类型兼容则向下转发并映射.
我在Alexandrescu的Modern C Design中一直在阅读的一些技巧看起来似乎可以适应这种情况,但关键是在运行时做一些决策.将指针传递给模板函数,推断它们的类型然后做一些元编程魔术会很棒,但我认为它会选择基于基类指针而不是派生类型的模板特化.有没有办法在基类中实现一个身份函数,它将返回一个基于派生模板类的协变返回类型?
CRTP似乎是解决虚拟模板函数问题的一种很酷的方法,但是在存储Base指针时我仍然需要知道派生类型.我可以从非模板基类继承,但后来我们回到了第一个方块.感觉这个障碍会抑制基于策略的设计方法.如果没有在具有不同策略的类型之间维护公共接口,如何从多个策略组成对象.每种具体类型都有不同的模板参数.如果你需要做的就是通过一个简单的函数调用来调用一些副作用,它可以很好地继承基类,但是比这更先进的东西看起来不切实际.
关于如何在不牺牲运行时效率,未来可扩展性和某种程度的类型安全性的情况下解决这种类型的架构,我感到有些困惑.在以前的经典vanilla c范例中似乎没有一个简单的解决方案,我只是对模板技巧和功能方法知之甚少,无法知道从哪里开始查找或搜索什么.
我希望有人可以指出我正确的方向或建议一种方法.
最佳答案 我开发了一个名为
DSPatch的现代C流程编程库,它运行面向对象的API,就像你所描述的那样.我认为你会发现最有趣的类是
RunType类 – 用于向组件传送数据和从组件传送数据.
RunType类使用内部模板类和公共模板方法,允许用户根据需要获取和设置包含的变量.因为包含的变量是内部模板化的,所以它能够在运行时更改类型.
现在,为了避免在get / set期间尽可能多的开销,我只在类型更改时使用typeid(),然后为后续的get / set类型匹配缓存类型 – 请参阅RunType :: CopyFrom().在值移动而不是复制的情况下,我们根本不需要匹配类型 – 请参阅RunType :: MoveTo().
在可能的情况下,DSPatch中的组件之间的数据通过移动而不是复制进行传输.
希望这可以帮助!