Что является хорошей альтернативой этой уродливой конструкции, в С++?

Это мой код (упрощение реальной проблемы):

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 */ }
}

Как я могу сделать свой код на С++ таким же элегантным, как этот пример PHP? Спасибо заранее.

Ответы

Ответ 1

В С++ нет отражения. Однако что-то вроде 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); }
};

Ответ 2

Вы можете использовать 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;
        }
    }
}

Смотрите также этот вопрос SO: Как вы реализуете таблицу отправки на выбранном вами языке?

Ответ 3

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

Ответ 4

Следуя предложению Александра С., вы можете объединить подход 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...

Ответ 5

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

Ответ 6

Я поддерживал Alexandre C, но у меня есть оговорки о создании структуры данных во время выполнения (заполнение std:: map), когда все данные известны во время компиляции.

Я поддерживал the_void, но линейный поиск подходит только для относительно небольших наборов данных.

Один из вариантов, заслуживающий рассмотрения, - это script (написанный, например, Python), для генерации таблицы хеш-таблицы или идеально сбалансированного двоичного дерева или любого другого во время сборки. Вы будете делать это только в том случае, если у вас есть постоянная потребность в поддержке больших наборов данных с известным компилятором, конечно.

В С++, вероятно, существуют шаблонные способы обхода, они полностью завершены, и, по крайней мере, один генератор модели состояния компилятора времени, который явно более сложный, чем хэш-таблица или двоичное дерево. Но лично я бы не рекомендовал его. Генерирующий код script будет более простым и надежным.

У меня есть script для создания тернарных деревьев, но (1) он здесь немного длинный и (2) его не совсем яркий пример хорошего кодирования.

Ответ 7

У вас есть несколько возможностей. Но первое, что я должен сказать, это то, что С++ строго типизирован. Поэтому метод, который обрабатывает экземпляр Foo, с одной стороны, и Foo, с другой стороны, отличается от метода, который обрабатывает Foo и Bar.

Теперь предположим, что вы хотите обрабатывать объекты 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);

Это довольно утомительная работа.

Ответ 8

Есть способы делать подобные вещи на С++ с массивами и динамической рассылкой.

Что вы делаете, так это создать абстрактный класс с некоторым стандартным действием(), например:

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