У меня есть набор классов аппаратных обработчиков, все они получены из базового класса, которые должны отвечать на входящий пакет данных. Часть этого пакета представляет собой строку ASCII, которая определяет, какая функция-член аппаратного обработчика используется для обработки пакета (например, «вентилятор» будет выполнять ToggleFan()
функция.
class HardwareHandler {
virtual void dispatchCommand(const String& cmd) = 0;
}
class FooblerHandler : public HardwareHandler {
void toogleFan();
void dispatchCommand(const String& cmd) {
//is this a "good" way to do this?
if (cmd == "fan")
toggleFan();
}
}
Я использую JUCE в качестве основы, что означает, что у меня есть такие вещи, как шаблон HashMap
с и String
,
Тем не менее, я не могу придумать аккуратный способ выбора правильной функции-обработчика на основе этой строки. Конструкция
if (str == "hello")
FooCommand();
else if (str == "bar")
BarCommand();
Концептуально это выглядит мне некрасиво, поскольку в нем много сравнительно дорогих сравнений строк. Тем не менее, код легко написать, и логика хранится в одном месте для каждого класса.
Другая альтернатива, которую я попробовал, состоит в том, чтобы создать хэш-карту строк для перечисления и использовать это выражение switch:
switch (str.getHash())
{
case CmdFoo:
FooCommnad();
break;
....and so on
}
Однако это также требует от меня установки статической хэш-карты, а также для поддержания соответствия переключателя.
Еще кое-что, что я попробовал, это хэш-карта, формирующая строку для самого указателя на функцию-член, в надежде, что она сможет перейти непосредственно из строки в функцию-член, не перечисляя их в операторе case, а также позволяет использовать очень общую функцию диспетчеризации. , поскольку он просто должен искать в хэш-карте, ему даже не нужно знать все опции — они могут содержаться исключительно в хэш-карте, что позволяет мне, возможно, вставить функцию диспетчеризации в базовый класс обработчика. и не повторяться в каждом конкретном устройстве обработчик. Тем не менее, этот метод поставил меня в тупик, так как я не могу понять, как это сделать правильно, или даже если это возможно сделать со статической хэш-картой и функциями-членами.
Существует ли идиоматический способ отправки функций-членов на основе строки (или аналогичного типа, который трудно сравнивать), предпочтительно с максимально возможной логикой, которая может быть обобщена и перемещена в родительский класс, насколько это возможно?
Вот моя попытка. Вы можете инкапсулировать механизм отображения в класс:
#include <iostream>
#include <string>
#include <functional>
#include <map>
class X;
template<class X>
class handler_factory;
template<>
class handler_factory<X>
{
private:
using HandlerType = void (X::*)();
public:
handler_factory();
HandlerType get(const std::string& name) const
{
if (handlers.find(name) == handlers.end())
return nullptr;
else
return (*handlers.find(name)).second;
}
private:
std::map<std::string, HandlerType> handlers;
};
class X
{
public:
friend class handler_factory<X>;
private:
void f();
void h();
};
handler_factory<X>::handler_factory()
{
handlers["f"] = &X::f;
handlers["h"] = &X::h;
}
void X::f() { std::cout << "X::f();"; }
void X::h() { std::cout << "X::h();"; }
И ваш способ отправки может быть реализован как:
void dispatch_method(const std::string& name)
{
if (find_handler(name))
(this->*find_handler(name))();
}
int main()
{
X().dispatch_method("f");
}
куда find_handler
определяется как частный вспомогательный метод:
private:
auto find_handler(const std::string& name)
-> decltype(handler_factory<X>().get(name))
{
return handler_factory<X>().get(name);
}
Я думаю, что самый эффективный способ справиться с проблемой — это создать std::map
это отобразит ваши строки в соответствующие функции. Метод быстрый (благодаря алгоритму логарифмического поиска), простой и безопасный.
class FooblerHandler : public HardwareHandler {
typedef void (HardwareHandler::*function)();
map<string,function> commandMap;
void dispatchCommand(const string& cmd) {
if(commandMap.count(cmd))
(this->*commandMap.find(cmd)->second)();
else
cout << "No command found with name \"" <<cmd<< "\"." << endl;
}
};
И, конечно, вы должны инициализировать карту внутри конструктора (или где-то еще до его использования):
commandMap["fan"] = &FooblerHandler::toogleFan;
commandMap["someOtherCommand"] = &FooblerHandler::otherFunction;
Карты и включен в Стандартную библиотеку шаблонов (STL), которая поставляется практически всеми IDE.
РЕДАКТИРОВАТЬ:
Я не совсем прочитал текст в конце. Хорошо — теперь вы знаете синтаксис 🙂