我有一个带有状态机的类,并希望有一个单一的入口点来将事件传递给状态机。该事件伴随着事件特定数据,然后我想将其发送给处理程序。所以它看起来像这样......
class X
{
public:
...
template<typename... A> void fsm(eEvent eventId, const A&... eventData);
private:
...
void eventA(int a, double b);
void eventB(std::string a);
void eventC(unsigned long a);
};
...使用看起来像这样的调用......
X x;
x.fsm(eEventA, -1, 2.0);
x.fsm(eEventB, "xyz");
x.fsm(eEventC, 42);
我无法弄清楚如何让模板函数调用正确的处理程序。如果我只是打开eventId并传递变量参数,它将无法编译,因为所有参数组合都不存在处理程序(例如,没有eventA()处理程序接受eventB()参数,我不会无论如何都想要。)
我的猜测是,有一些优雅的方法可以做到这一点,但这是在逃避我。
[编辑]
好的,我发现了一种适合我的目的的方法。这是一个示例类来说明......
class X
{
private:
bool a(int) { return true; }
bool b(int) { return true; }
template<typename... A> bool a(const A&...) { return false; }
template<typename... A> bool b(const A&...) { return false; }
public:
template<typename... A> void x(int i, const A&... args)
{
if (i == 0)
{
a(args...);
}
else
{
b(args...);
}
}
};
a()和b()的可变参数模板方法将捕获对X.x()的任何调用,其中参数不是单个int。这解决了我收到的编译器错误,并启用了我正在寻找的更简单的类API。
答案 0 :(得分:4)
如果您没有C ++ 11模板参数包,那么自然的选择是为每个事件声明一个类型,从基本事件类型继承,并将eventData
包装在其中。这就是我推荐的。您可以使用动态类型来验证是否正在调度正确的内容,如果这样做会减慢速度,则禁用签入生产模式。
使用指向成员的指针函数向调度程序标识事件。这消除了枚举的需要,并将调度的类型直接编码到模板化的调度程序中。
但是,这确实要求调度函数为public
。
template<typename... P, typename... A> void fsm(void (X::*event)( P ... ),
const A&... eventData) {
this->*event( eventData ... );
}
x.fsm( &X::eventB, 5, 1.3 );
答案 1 :(得分:0)
通过保持相同的设计(即根据在运行时计算的整数值调度的事件),无法做你想要的。 eventId
的值仅在运行时已知,并确定要调用的函数的签名,但必须在编译时知道参数的 types 。 直接或间接,您的功能必须如下所示:
if (eventId == eEventA) { eventA(eventData...); }
else if (eventId == eEventB) { eventB(eventData...); }
...
但是将不会编译,因为编译器将解析 if
分支的每个,并且当然会找到对eventX
的一些调用与其签名不兼容。
我建议您首先不要使用此设计,而是采用类似于Boost.Signal的方法来处理事件。
答案 2 :(得分:0)
我不确定我是否完全理解你想要做什么,但是这个模板专业化会有用吗?
#include <iostream>
// ----------------------------------
class EventA
{
public:
int a;
double b;
};
class EventB
{
public:
std::string strEvent;
};
class EventC
{
public:
unsigned long a;
};
// -------------------------
class X
{
public:
template<typename EVENT_TYPE> void fsm (const EVENT_TYPE &eventData);
public:
void eventA (int a, double b) {}
void eventB (std::string a) {}
void eventC (unsigned long a) {}
};
// -------------------------
template<typename TYPE>
struct invoker {};
template<>
struct invoker<EventA>
{
static void call (X *x, EventA e)
{
x->eventA (e.a, e.b);
}
};
template<>
struct invoker<EventB>
{
static void call (X *x, EventB e)
{
x->eventB (e.strEvent);
}
};
template<>
struct invoker<EventC>
{
static void call (X *x, EventC e)
{
x->eventC (e.a);
}
};
// ---------------------------------------
template<typename EVENT_TYPE>
void X::fsm (const EVENT_TYPE &eventData)
{
invoker<EVENT_TYPE>::call (this, eventData);
}
int main (int argc, char *argv[])
{
X x;
EventA eEventA;
eEventA.a = -1;
eEventA.b = 2.0;
EventB eEventB;
eEventB.strEvent = "xyz";
EventC eEventC;
eEventC.a = 42;
x.fsm (eEventA);
x.fsm (eEventB);
x.fsm (eEventC);
return 0;
}
答案 3 :(得分:0)
如果eEvent类似于整数并且在编译时始终是已知的,那么您可以对其进行模板化。然后你的调用看起来像
x.fsm<eEventA>(1, 2.4);
由于对特殊功能模板的限制,它可能会变得更复杂一些。此外,您可能需要一个泛型,这意味着错误的参数包将是一个运行时错误(但是一个明确的错误)。
我不确定这实际上有什么优势
x.onEventA(1, 2.4);