2014-02-05 3 views
2

У меня есть существующий код структуры, как это:Замена композитных виртуальных шаблонов с

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 или получилось бы действительно уродливым, хотя, так что я в порядке с этим не делаю.)

ответ

1

Вы можете получить его значительно удобнее с помощью Boost, Fusion для алгоритмической части и увеличить 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

+1

Просто заметил тег [tag: C++ 03], поэтому [** вот версия C++ 03: ** coliru.stacked-crooked.com/a/16f97e40c0827777] (http://coliru.stacked-crooked.com/a/16f97e40c0827777) – sehe

+0

Выглядит намного приятнее. Я попробую это завтра. – Miral

+0

Кажется хорошо работать. Однако производительность не так сильно улучшилась, как я бы надеялся. Я думаю, что распределитель памяти был более эффективным, чем я ожидал. – Miral

2

После некоторых экспериментов (вдохновлено this и this), я придумайте следующее. Кажется, что это работает, но мне все еще интересно узнать, можно ли это улучшить (или заменить что-то лучше).

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

В частности, я не уверен, что мне нравится, чтобы определить объект функции для каждого делегированного метода, и мне интересно, если есть какой-то способ использовать привязку, чтобы скрыть некоторые из этого.

+0

+1 Хорошая работа. Надеюсь, вам понравится мой более высокий уровень подхода :) – sehe

Смежные вопросы