我使用boost msm库创建了一个有限状态机。事件及其转换在编译时以声明方式定义。但是,在运行时,需要有一些代码根据输入数据选择正确的事件。目前代码如下所示:
enum : unsigned {
fin = (1 << 0),
syn = (1 << 1),
ack = (1 << 4)
// etc...
};
// events
struct receive_syn {};
struct receive_syn_ack {};
struct receive_fin {};
struct receive_fin_ack {};
struct receive_ack {};
// etc..
void receive(const Segment& segment)
{
switch (segment.getFlags())
{
case syn|ack: state_machine.process_event(receive_syn_ack{}); break;
case syn: state_machine.process_event(receive_syn{}); break;
case fin|ack: state_machine.process_event(receive_fin_ack{}); break;
case fin: state_machine.process_event(receive_fin{}); break;
case ack: state_machine.process_event(receive_ack{}); break;
// etc..
}
}
它有效并且可能很快。但是,我觉得这应该用更具说明性的风格来写。
如何在更高级别的编程风格中实现它而不会引入额外的运行时开销?
供参考,这是一个boost::msm example。
答案 0 :(得分:3)
警告!未经测试。
您可以使用编译时映射将标志连接到要传递的对象类型:
template < unsigned tflags, typename ttype >
struct kv
{
static const unsigned flags = tflags;
using type = ttype;
};
using flag_type_map = std::tuple
<
kv<syn|ack, receive_syn_ack>,
kv<syn , receive_syn >,
kv<fin|ack, receive_fin_ack>,
kv<fin , receive_fin >,
kv<ack , receive_ack >
>;
为了在运行时处理它,您必须进行某种迭代或递归。如果编译器足够智能(和内联),则可以获得相同的性能。
template < typename >
struct tuple_pop;
template < typename T, typename... TT >
struct tuple_pop < std::tuple < T, TT... > >
{
using type = std::tuple < TT... >;
};
template < typename T >
void call(unsigned flags, std::true_type)
{
throw std::invalid_argument("flag combination not known / invalid");
}
template < typename T >
void call(unsigned flags, std::false_type = {})
{
using tuple_first = typename std::tuple_element<0, T>::type;
using tuple_popped = typename tuple_pop<T>::type;
using is_last = std::integral_constant<bool,
0 == -1+std::tuple_size<T>::value >;
if(flags == tuple_first::flags)
{
// could replace this hard-wired call with a passed function object
// to make it more generic
state_machine.process_event( typename tuple_first::type{} );
}else
{
create_obj<tuple_popped>(flags, is_last{});
}
}
void receive(const Segment& segment)
{
call<flag_type_map>(segment.getFlags());
}
答案 1 :(得分:2)
我认为以下内容应该有效(未经测试的代码),并且没有太多标志应该仍然非常有效 - O(log n),但是迭代速度很快:
// your enum
enum: unsigned
{
fin = (1 << 0),
syn = (1 << 1),
ack = (1 << 4)
// etc...
};
// this is used for the magic:
unsigned all = fin|syn|ack|...;
// this replaces your individual receive types:
template<unsigned> struct receive {};
// this is the magic translation to compile time
template<unsigned bit = 1, unsigned mask = all, unsigned value = 0> struct call
{
void process(state_machine_type& state_machine, unsigned flags)
{
if (flags & bit)
call<(bit << 1), mask & ~bit, value | bit>::process(state_machine, flags);
else
call<(bit << 1), mask & ~bit, value>::process(state_machine, flags);
}
};
template<unsigned bit, unsigned value> struct call<bit, 0, value>
{
void process(state_machine_type& state_machine, unsigned)
{
state_machine.process_event(receive<value>{});
}
};
// the rewrite of your receive function
void receive(const Segment& segment)
{
call<>::process(state_machine, segment.getFlags());
}