首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用逻辑运算符组合实现C++布尔函数对象

用逻辑运算符组合实现C++布尔函数对象
EN

Code Review用户
提问于 2012-10-24 12:26:40
回答 1查看 1.6K关注 0票数 4

我想要使C++对象层次结构的“分类器”,它可以一起通过逻辑运算符组成一个单一的分类器,实现整个逻辑组合。

这实际上是一个粒子物理分析库,但我将给出一个简单得多的例子,它只对整数进行分类。其动机是,待分类的候选物理对象可能具有许多浮点特性,可以保留或丢弃它。例如质量、能量、角度等--我不可能创建所有可能的函数签名组合来处理这个问题,即使我可以,编译器也无法确定例如“第三个双平均质量或角度吗?”因此,这样做的目的是使用户的代码更清晰、更少模棱两可,同时减少我的API中的函数签名数量,并增加灵活性。

对于整数分类器示例,下面是我希望得到的结果用法的示例:

代码语言:javascript
复制
Classifier c = IsOdd() || (IsEven() && LessThan(6));
for (int i = 0; i < 10; ++i) {
    if (c.classify(i)) cout << i << " ";
}
// prints 0 1 2 3 4 5 7 9

这说明了几项要求:

  • 我希望能够在一个表达式中完成多个逻辑组合。
  • 对象都是堆栈分配的--没有涉及到news来搞乱语法。new IsOdd() || (new IsEven() ... )既会分散注意力,又会给用户带来内存管理问题。
  • 逻辑运算符应该有其通常的含义、先例和懒惰的评估行为:在复合分类器上调用classify应该在每个逻辑组合链上调用相同的函数,并尽快停止。
  • 具体分类器(IsOddLessThan等)应该是状态对象,例如传递给LessThan的构造函数参数,而不必对每个整数都有不同的LessThanX类!
  • 理想情况下,基类应该能够被实例化,而不是必须通过(const)引用来处理。

我已经做了几次尝试来实现这一点,但是不断地遇到问题。通常这些都与多态相关:如果每个Classifier都包含其他分类器(S),那么它们需要作为指针保存(引用不能存储),我们回到内存管理问题上。我找到了一种利用dynamic_any扩展来增强其任何的方法:

Infrastructure:

代码语言:javascript
复制
#include <boost/any.hpp>
#include <dynamic_any.hpp>
using boost::any_cast;
using boost::dynamic_any_cast;

#include <string>
#include <vector>
#include <iostream>
#include <cassert>
#include <utility>
using namespace std; // for clarity only, won't go in any public header!


/// Main types

class Classifier {
public:
  virtual bool classify(int a) const = 0;
};


class ClassifierAND : public Classifier {
public:
  bool classify(int a) const {
    return dynamic_any_cast<const Classifier&>(classifiers.first).classify(a)
      && dynamic_any_cast<const Classifier&>(classifiers.second).classify(a);
  }
  pair<boost::dynamic_any, boost::dynamic_any> classifiers;
};

class ClassifierOR : public Classifier {
public:
  bool classify(int a) const {
    return dynamic_any_cast<const Classifier&>(classifiers.first).classify(a)
      || dynamic_any_cast<const Classifier&>(classifiers.second).classify(a);
  }
  pair<boost::dynamic_any, boost::dynamic_any> classifiers;
};


/// Operator overloads

template <typename Classifier1, typename Classifier2>
inline ClassifierAND operator && (const Classifier1& c1, const Classifier2& c2) {
  ClassifierAND rtn;
  rtn.classifiers.first = c1;
  rtn.classifiers.second = c2;
  return rtn;
}

template <typename Classifier1, typename Classifier2>
inline ClassifierOR operator || (const Classifier1& c1, const Classifier2& c2) {
  ClassifierOR rtn;
  rtn.classifiers.first = c1;
  rtn.classifiers.second = c2;
  return rtn;
}

具体分类器:

代码语言:javascript
复制
struct IsEven : public Classifier {
  bool classify(int a) const { return a % 2 == 0; }
};

struct LessThan : public Classifier {
  LessThan(int val) { cutval = val; }
  bool classify(int a) const { return a < this->cutval; }
  int cutval;
};

struct GtrThan : public Classifier {
  GtrThan(int val) { cutval = val; }
  bool classify(int a) const { return a > this->cutval; }
  int cutval;
};

测试程序:

代码语言:javascript
复制
void test(const Classifier& c) {
  for (int i = -3; i < 10; ++i) {
    if (c.classify(i)) cout << i << " ";
  }
  cout << "\n";
}


int main() {

  IsEven e;
  GtrThan g4(4);
  GtrThan g2(2);
  LessThan l(-1);

  const Classifier& c1 = e || GtrThan(4);
  test(c1);
  // -2 0 2 4 5 6 7 8 9

  ClassifierAND c2 = e && g2 && g4;
  test(c2);
  test(e && g2 && g4);
  // 6 8 (twice)

  test(e && l);
  // -2

  const Classifier& c4 = IsEven() || LessThan(3);
  test(c4);
  test(IsEven() || LessThan(3));
  // -3 -2 -1 0 1 2 4 6 8 (twice)

}

从输出上看,这确实有效..。但我认为/希望它还能得到改进。特别是,Classifier基类是虚拟的,这迫使我使用const引用:罚款作为参数签名(cf )。( test),但对于显式变量(如上一次测试)来说有点混乱,如果不小心使用,可能会出现“对象切片”错误。

此外,ClassifierAND/OR类型真的很笨重--我最初尝试在基类中放置一个"next“Classifier成员和一个逻辑操作枚举,以避免这些对,但无法使它很好地工作。最好将它们隐藏起来,或者完全避免它们,这样编译器就不会发出警告,说明一个逻辑组合的分类器链是用户不公开的类型。

我也不确定是否可以提高效率:由于和/或对,通常会有相当多的对嵌套。

任何反馈意见和改进建议将是非常欢迎的!

EN

回答 1

Code Review用户

发布于 2012-10-24 21:14:28

我认为使用模板和鸭类型将获得更好的成功,而不是多态界面。C++标准库使用鸭子类型来实现您在这里试图实现的相同影响。

这就是我要做的:

代码语言:javascript
复制
// A couple of these are already in the standard library have a look.
struct IsEven
{
  bool operator()(int a) const { return a % 2 == 0; }
};

struct LessThan
{
  LessThan(int val): cutval(val)  {}
  bool operator()(int a) const { return a < cutval; }
  int cutval;
};

struct GtrThan
{
  GtrThan(int val): cutval(val) {}
  bool operator()(int a) const { return a > this->cutval; }
  int cutval;
};

template<typename T1, typename T2>
struct AndOp
{
    AndOp(T1 const& t1, T2 const& t2) : t1(t1), t2(t2) {}
    T1 const    t1;
    T2 const    t2;
    bool operator()(int a) const { return t1(a) && t2(a);}
};

template<typename T1, typename T2>
struct OrOp
{
    OrOp(T1 const& t1, T2 const& t2) : t1(t1), t2(t2) {}
    T1 const    t1;
    T2 const    t2;
    bool operator()(int a) const { return t1(a) || t2(a);}
};

template<typename T1, typename T2>
AndOp<T1, T2> make_and_op(T1 const& t1, T2 const& t2)
{
    return AndOp<T1, T2>(t1, t2);
}
template<typename T1, typename T2, typename T3>
AndOp<AndOp<T1, T2>, T3> make_and_op(T1 const& t1, T2 const& t2, T3 const& t3)
{
    return AndOp<AndOp<T1, T2>, T3>(make_and_op(t1, t2), t3));
}

template<typename T1, typename T2>
OrOp<T1, T2> make_or_op(T1 const& t1, T2 const& t2)
{
    return OrOp<T1, T2>(t1, t2);
}

现在我们可以用以下方法进行测试:

代码语言:javascript
复制
template<typename T>
void test(const T& c) {
  for (int i = -3; i < 10; ++i) {
    if (c(i)) cout << i << " ";
  }
  cout << "\n";
}

int main()
{

  test(make_or_op(IsEven(), GtrThan(4)));
  // -2 0 2 4 5 6 7 8 9

  test(make_and_op(IsEven(), GtrThan(2), GtrThan(4)));
  // 6 8

  test(make_and_op(IsEven(), LessThan(-1)));
  // -2

  test(make_or_op(IsEven(), LessThan(3))); 
  // -3 -2 -1 0 1 2 4 6 8
}
票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/17881

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档