【问题标题】:Replacing composite virtual with templates用模板替换复合虚拟
【发布时间】:2014-02-05 05:05:16
【问题描述】:

我有一些结构如下的现有代码:

class IRule
{
public:
    virtual ~IRule() {}
    virtual void Begin(int value) = 0;
    virtual double Evaluate(Context& context) = 0;
};

class RuleA : public IRule
{
     // concrete implementation
};
// more rules; note many require non-default construction and extra setup

class CompositeRule : public IRule
{
public:
    // called any number of times to add child rules
    void Add(IRule *rule) { rules.push_back(rule); }

    virtual void Begin(int value) { /* for each rule, call Begin */ }
    virtual double Evaluate(Context& context)
    {
        /* for each rule, call Evaluate and sum the results */
    }
private:
    std::vector<IRule*> rules;
};

void DoSomething(IRule *rule)
{
    rule->Begin(x);
    ProcessResult(rule->Evaluate(y));
}

当然,DoSomething 可以被赋予单个规则或某些规则组合。此代码有效,但它需要分配内存来创建和填充CompositeRule(对于虚拟对象本身和包含它们的向量),并且这是在导致性能问题的循环中发生的。 (将规则构造移到循环之外是不可行的。)

我想通过将CompositeRule 替换为模板类来解决此问题,该模板类直接保存其每个子规则具体类型的实例(因此它可以在堆栈而不是堆上构建)。虽然DoSomething 是从几个不同的地方用不同的规则集调用的,但在每个调用站点,这个集合在编译时是固定的,所以这应该是可行的。 (甚至可能完全移除虚拟基础,尽管这需要将 DoSomething 设为模板,而且我不确定是否要这样做。)

写这类东西的最佳方式是什么,Boost 中有什么东西可以帮助解决这个问题吗? (MPL、Tuple 和 Fusion 似乎是可能的候选者,但我从来没有真正玩过它们。)我假设我可能必须使所有规则默认可构造,但如果有某种方法可以给构造函数- 构造复合材料时将参数设置为单个规则,那会很好。 (我怀疑这要么需要 C++11 转发,要么会变得非常丑陋,所以我可以不这样做。)

【问题讨论】:

    标签: c++ templates boost c++03


    【解决方案1】:

    经过一些实验(受thisthis 的启发),到目前为止,我提出了以下建议。它似乎有效,但我仍然有兴趣看看这是否可以改进(或用更好的东西代替)。

    template<typename TRules>
    class CompositeRule : public IRule
    {
        typedef typename boost::mpl::reverse_fold<TRules, boost::tuples::null_type, 
                boost::tuples::cons<boost::mpl::_2, boost::mpl::_1> >::type tuple_type;
        typedef boost::mpl::range_c<int, 0, boost::tuples::length<tuple_type>::value> tuple_range;
    
        tuple_type m_Rules;
    
        struct invoke_begin
        {
            tuple_type& rules;
            int value;
    
            invoke_begin(tuple_type& rs, int val) : rules(rs) : value(val) {}
    
            template<typename N>
            void operator()(N) { boost::tuples::get<N::value>(rules).Begin(value); }
        };
    
        struct invoke_evaluate
        {
            tuple_type& rules;
            Context& context;
            double result;
    
            invoke_evaluate(tuple_type& rs, Context& ctx) : rules(rs), context(ctx), result(0) {}
    
            template<typename N>
            void operator()(N) { result += boost::tuples::get<N::value>(rules).Evaluate(context); }
        };
    
    public:
        virtual void Begin(int value)
        {
            boost::mpl::for_each<tuple_range>(invoke_begin(m_Rules, value));
        }
    
        virtual double Evaluate(Context& context)
        {
            invoke_evaluate f(m_Rules, context);
            boost::mpl::for_each<tuple_range>(boost::ref(f));
            return f.result;
        }
    };
    

    我不确定我是否喜欢为每个委托方法定义一个函数对象,我想知道是否有某种方法可以使用 bind 来隐藏其中的一些。

    【讨论】:

      【解决方案2】:

      算法部分使用 Boost Fusion,绑定部分使用 Boost Phoenix,这样会更方便:

      namespace phx = boost::phoenix;
      namespace fus = boost::fusion;
      
      template<typename... R>
      class CompositeRule : public IRule
      {
          std::tuple<R...> m_rules;
        public:
          CompositeRule(R... rules) : m_rules(rules...) {}
      
          virtual void Begin(int value) {
              fus::for_each(m_rules, phx::bind(&IRule::Begin, arg1, value));
          }
      
          virtual double Evaluate(Context& context) {
              return fus::accumulate(m_rules, 0.0, arg1 + phx::bind(&IRule::Evaluate, arg2, phx::ref(context)));
          }
      };
      

      不再需要低级模板元编程:/

      为了奖励,添加一个不错的工厂函数:

      template<typename... R>
      CompositeRule<R...> make_composite(R&&... rules)
      {
          return CompositeRule<R...>(std::forward<R>(rules)...);
      }
      

      所以你可以有完整的类型推导:

      int main()
      {
          auto combine(make_composite(RuleA(20), RuleA(), RuleA(100)));
          DoSomething(&combine);
      
          // you can even re-compose:
          auto more(make_composite(combine, RuleA(-200), combine, combine, combine));
          DoSomething(&more);
      }
      

      Live On Coliru

      检查输出:(585.12 + 2 * 200) ÷ 246.28 == 4

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-12-24
      • 2015-01-04
      • 2016-04-12
      • 1970-01-01
      • 2015-12-21
      • 1970-01-01
      • 1970-01-01
      • 2013-01-22
      相关资源
      最近更新 更多