Что является хорошей альтернативой этой уродливой конструкции, в С++?
Это мой код (упрощение реальной проблемы):
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", <},
{"lte", <e},
{"gt", >},
{"gte", >e},
(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();