Domanda

I have some existing code with a structure like this:

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

The idea of course is that DoSomething can be given either a single rule or some combination of rules. This code works, but it requires memory allocation to create and populate the CompositeRule (both for the virtual objects themselves and for the vector that contains them), and this is occurring in a loop where it is causing performance problems. (It's not feasible to move the rule construction outside of the loop.)

I want to resolve this by replacing the CompositeRule with a templated class that holds an instance of each of its child rule concrete types directly (so it can be constructed on the stack rather than the heap). While DoSomething gets called from a few different places with different sets of rules, at each call-site this set is fixed at compile-time, so this ought to be feasible. (Possibly even removing the virtual base entirely, although that would require making DoSomething a template, and I'm not sure I want to do that.)

What's the best way of writing this sort of thing, and is there something in Boost that can help with this? (MPL, Tuple, and Fusion seem like possible candidates, but I've never really played with any of them.) I assume that I'll probably have to make all the Rules default-constructable, but if there's some way to give constructor-parameters to individual rules when constructing the composite, that would be nice. (I suspect that this would either require C++11 forwarding or would get really ugly, though, so I'm ok with not doing that.)

È stato utile?

Soluzione 2

You can get it considerably more convenient by using Boost Fusion for the algorithmic part and Boost Phoenix for the bind part:

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

No more low-level template meta programming :/

For bonus, throw in a nice factory function:

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

So you can have full type deduction:

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

See it Live On Coliru

Check the output: (585.12 + 2 * 200) ÷ 246.28 == 4

Altri suggerimenti

After some experimentation (inspired by this and this), I've come up with the following so far. It seems to work, but I'm still interested to see if this can be improved (or replaced with something better).

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

In particular I'm not sure I like having to define a function object for each delegated method, and I'm wondering if there's some way to use bind to hide some of that.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top