我有一些现有的代码,结构如下:
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转发,或者会变得非常难看,所以我没有这样做。)
答案 0 :(得分: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;
}
};
特别是我不确定我是否要为每个委托方法定义一个函数对象,我想知道是否有某种方法可以使用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);
}
检查输出:(585.12 + 2 * 200) ÷ 246.28 == 4