在c ++中,这个丑陋的构造有什么好的替代方案?

时间:2010-06-29 12:43:29

标签: c++

这是我的代码(简化现实生活中的问题):

class Foo {
public:
  void f(const string& s) {
    if (s == "lt") {
      return lt();
    } else if (s == "lte")
      return lte();
    } else if (s == "gt")
      return gt();
    } else if (s == "gte")
      return gte();
    }
  }
  void lt() { /* skipped */ }
  void lte() { /* skipped */ }
  void gt() { /* skipped */ }
  void gte() { /* skipped */ }
};

这就是我用PHP / Python / JavaScript /许多其他语言(PHP中的例子)的方法:

class Foo {
  function f($s) {
    return $this->$s();
  }
  function lt() { /* skipped */ }
  function lte() { /* skipped */ }
  function gt() { /* skipped */ }
  function gte() { /* skipped */ }
}

如何使我的C ++代码像这个PHP示例一样优雅?提前谢谢。

8 个答案:

答案 0 :(得分:37)

C ++中没有反映。但是,像std::map<std::string, void (Foo::*)()>这样的东西应该可以解决问题。


编辑:这是一些难以实现的代码。请注意以下事项:

  • 这可能会以各种方式改进
  • 请添加代码以处理不存在的令牌。我没有做错误检查。

#define BEGIN_TOKEN_MAP \
template <int n> \
struct add_to_ \
{ \
    static void act() {} \
}; \
std::map<std::string, void (Foo::*)()> map_;


#define DECLARE_TOKEN(str, n) \
template <> struct add_to_<n> \
{ \
    static void act() { map_[#str] = &Foo::##str; add_to<n+1>::act();} \
};\
void str()

#define END_TOKEN_MAP \
void init_map() { add_to_<0>::act(); } \
void process_token(std::string s) { (this->*map_[s])(); }


class Foo
{
    BEGIN_TOKEN_MAP
    DECLARE_TOKEN(lt, 0) { ... }
    DECLARE_TOKEN(gt, 1) { ... }
    ...
    END_TOKEN_MAP

    Foo() { init_map(); }
    void f(const std::string& s) { process_token(s); }
};

答案 1 :(得分:12)

您可以使用dispatch table之类的:

typedef struct {
    char *name;
    void (*handler)();
} handler_t;

handler_t *handlers = {
    {"lt", &lt},
    {"lte", &lte},
    {"gt", &gt},
    {"gte", &gte},
    (NULL, NULL}
};

void f(const string &s) {
    for (int i=0; handlers[i].handler; ++i) {
        if (0 == strcmp(s.c_str(), handlers[i].name)) {
            handlers[i].handler();
            return;
        }
    }
}

另见这个问题:How do you implement a dispatch table in your language of choice?

答案 2 :(得分:5)

C ++不是动态的,所以没有确切的等价物。更优雅的是使用地图和可能的功能对象。

答案 3 :(得分:4)

根据Alexandre C.的建议,您可以将std::map<...方法与operator()结合使用,以避免调用void Foo::f

例如:

class Foo {
   private:
      map<string,void (Foo::*)()> funs;
   public:
      // constructors etc.
      void operator () (const string& s) {
         if (funs.find (s) != funs.end ())
            (this->*funs[s])();
      }
      // remainder
};

现在你可以使用类似于

的foo
Foo f;
f("lt");  // calls Foo::lt ()
f("lte"); // calls Foo::lte ();
// etc...

答案 4 :(得分:3)

// Beware, brain-compiled code ahead!
namespace {
  typedef std::map<std::string, void (Foo::*)()> operations_map_t;
  typedef operations_map_t::value_type operations_entry_t;

  const operations_entry_t* operations = { {"lt" , &Foo::lt }
                                         , {"lte", &Foo::lte}
                                         , {"gt" , &Foo::gt }
                                         , {"gte", &Foo::gte} };
  const operations_map_t operations_map( operations 
                                       , operations + sizeof(operations)
                                                    / sizeof(operations[0]) );
}

void Foo::f(const string& s)
{
  operations_map_t::const_iterator it = operations_map.find(s);
  if(it == operations_map.end()) throw "Dooh!";
  it->second();
}

答案 5 :(得分:2)

我赞成了Alexandre C,但我对在运行时构建数据结构(填充std :: map)有所保留,当数据在编译时都是已知的时候。

我赞成了the_void,但线性搜索仅适用于相对较小的数据集。

值得考虑的一个选项是脚本(用Python编写)来生成散列表或完美平衡的二叉树或构建时的任何内容。当然,如果您经常需要支持大型已知的编译时数据集,那么您将只会这样做。

在C ++中可能有模板技巧的方法 - 它们是Turing完成的,并且至少有一个编译时解析器状态模型生成器,它显然比散列表或二叉树更复杂。但就个人而言,我不会推荐它。代码生成脚本将更简单,更健壮。

我有一个用于生成三元树的脚本,但是(1)这里有点长,而且(2)它不是一个良好编码的光辉榜样。

答案 6 :(得分:1)

你有几种可能性。但我要说的第一件事是C ++是强类型的。因此,一方面处理Foo的实例而另一方面处理Foo的方法与处理FooBar的方法的类型不同。< / p>

现在,我们假设你只想处理Foo个对象。那么你有2个解决方案:

  • 函数指针
  • 功能对象

函数对象更通用,值得注意的是,它允许您在一个对象中指定多个参数组合。

class OperatorBase
{
public:
  virtual ~OperatorBase() {}

  bool operator()(Foo const& lhs, Foo const& rhs) const;
  bool operator()(Foo const& lhs, Bar const& rhs) const;
  bool operator()(Bar const& lhs, Foo const& rhs) const;
  bool operator()(Bar const& lhs, Bar const& rhs) const;
private:
  // virtual methods to actually implement this
};

struct LessThanOperator: OperatorBase
{
  // impl
};

class OperatorFactory
{
public:
  static OperatorBase& Get(std::string const& name);

  template <class T>
  static void Register(std::string const& name);
private:
  typedef boost::ptr_map<std::string, OperatorBase> ops_t;
  static ops_t& Get() { static ops_t O; return O; }
};

然后你可以继续:

// Choose the operator
OperatorBase& op = OperatorFactory::Get("lt");

Foo foo;
Bar bar;

bool const result = op(foo, bar);

虽然这是相当繁琐的工作。

答案 7 :(得分:0)

有很多方法可以在C ++中使用数组和动态调度来执行类似的操作。

你要做的是创建一个带有一些标准action()的抽象类,如下所示:

class abstract_handler {
public:
   virtual void action () = 0;
}

然后使用action()的不同实现创建子类。例如,对于您的“ffa”分支,您可以写:

class ffa_handler : public abstract_handler {
public:
   virtual action() {
      // Do your custom "ffa" stuff in here
   }

   // Add your custom "ffa" members for action() to work on here.
   // ...and of course a constructor to initialize them.
}

然后,您创建一个指向每个类对象的指针的映射(在您的情况下,由std::string索引)。在启动时,您使用正确的字符串索引上的适当对象填充它。然后在运行时,您所要做的就是:

handler_map[index_string].action();