用模板替换复合虚拟

时间:2014-02-05 05:05:17

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

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

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转发,或者会变得非常难看,所以我没有这样做。)

2 个答案:

答案 0 :(得分:2)

经过一些实验(灵感来自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来隐藏其中一些。

答案 1 :(得分:1)

通过将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