【问题标题】:Selecting a function overload based on runtime data?根据运行时数据选择函数重载?
【发布时间】:2013-06-05 19:58:20
【问题描述】:

我使用 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

【问题讨论】:

  • 我推荐一个函数指针的哈希表,这是我在这种情况下通常做的第一件事。哈希表的查找速度为 O(1),因此它的性能甚至比您当前的实现还要好。
  • 编译器可能会将开关转换为查找表;在这种情况下,当前实现的性能不会比哈希表差。
  • 我怀疑哈希值的计算加上函数指针的调用会比开关更昂贵。另外O(1) 不一定比O(n) 快​​。 Big-O 是增长率的特征。在我的情况下,N 相对较小(
  • 如果你将枚举的值设为一个连续的整数序列,那么枚举可以用作表的索引。
  • receive_syn_ack 等是空类型吗?

标签: c++


【解决方案1】:

警告!未测试。

您可以使用编译时映射将标志连接到要传递的对象类型:

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());
}

【讨论】:

    【解决方案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());
    }
    

    【讨论】:

    • 你也应该得到 +1。我想知道编译器在使用优化时会对我们的尝试做出什么。 (当然,你的无优化运行时更好)
    • 仅供参考:optimized vs unoptimized 生成的程序集。有很大的不同。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-24
    • 2018-09-23
    相关资源
    最近更新 更多