Здравствуйте, ononim, Вы писали:
O>CommandHandler command_handlers[MAX_COMMAND_ID] — будет даже быстрее чем switch
В общем случае не будет.
switch давно уже компиляторами раскладывается в jmp по адресу из таблицы, если значения меток подходят для создания такой таблицы.
switch — конструкции, в особенности если cmd_1....cmd_N имеют линейный порядок очень не плохо оптимизирует сам компилятор.
Думаю что Вы это хорошо знаете.
В одной кнторе в которой Я работал, был такой бооольшой свитч по внутренним состояниям приложения... сперва думали переписать это
, потом стало ясно что не будем это делать, поотому что время на это нужно было затратить массу да и компилятор не плохо справлялся тут без нас.
Довольно часто, по крайней мере у меня в проектах, есть некоторый протокол через который приложение
общается с внешним миром. Протокол состоит из команд, которых может быть масса.
Соответсвенно в программе есть немерянный switch()... на каждую команду соответственно.
Примерно такой код
void process_command( long cmd_id, .... )
{
switch( cmd_id )
{
case cmd_1: process_cmd_1(...); break;
....
case cmd_N: process_cmd_N(...); break;
}
}
Каждый раз когда я вижу такой switch — case на несколько экранов приходит непреодолимое желание
изменить этот код.
Что хотелось бы, так это избавиться от такой конструкции, заменив это чем нибудь другим.
Вопрос производительности пока не принципиален. Интересны именно подходы.
Мой набросок выглядит примерно так ( пока нигде не применялся )
Здравствуйте, Sni4ok, Вы писали:
S>а просто std::map<long, boost::function<...> > не пойдёт?
Наверное пойдет как вариант...описанная проблема это только часть задачи.
Каждая команда при выполнении может принимать данные и может возвращать ответы, вообщем случае разные.
Здравствуйте, nen777w, Вы писали:
N>switch — конструкции, в особенности если cmd_1....cmd_N имеют линейный порядок очень не плохо оптимизирует сам компилятор. N>Думаю что Вы это хорошо знаете. N>В одной кнторе в которой Я работал, был такой бооольшой свитч по внутренним состояниям приложения... сперва думали переписать это N>, потом стало ясно что не будем это делать, поотому что время на это нужно было затратить массу да и компилятор не плохо справлялся тут без нас.
Вот вот... унаследованый код конечно никто трогать не собирается
Но на будущее хотелось бы иметь несколько возможных вариантов решения
Здравствуйте, nen777w, Вы писали:
N>switch — конструкции, в особенности если cmd_1....cmd_N имеют линейный порядок очень не плохо оптимизирует сам компилятор.
ИМХО константный, давно правда дисасемблер не смотрел
Здравствуйте, ioni, Вы писали:
I>Довольно часто, по крайней мере у меня в проектах, есть некоторый протокол через который приложение I>общается с внешним миром. Протокол состоит из команд, которых может быть масса. I>Соответсвенно в программе есть немерянный switch()... на каждую команду соответственно.
Предлагаю свой способ:
CommandHandler command_handlers[MAX_COMMAND_ID] — будет даже быстрее чем switch
Ну или std::map если производительность в самом деле совсем пофиг
Если хочется декларативности то можно извратиться примерно так:
Здравствуйте, ioni, Вы писали:
I>Довольно часто, по крайней мере у меня в проектах, есть некоторый протокол через который приложение I>общается с внешним миром. Протокол состоит из команд, которых может быть масса. I>Соответсвенно в программе есть немерянный switch()... на каждую команду соответственно. I>Примерно такой код I>
I>void process_command( long cmd_id, .... )
I>{
I> switch( cmd_id )
I> {
I> case cmd_1: process_cmd_1(...); break;
I> ....
I> case cmd_N: process_cmd_N(...); break;
I> }
I>}
I>
А просто паттерн "команда" применить разве нельзя?
Или паттерн "Стратегия"?
Они ж как раз для таких случаев и предназначены — для разруливания большого переключателя.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, LaptevVV, Вы писали:
LVV>А просто паттерн "команда" применить разве нельзя? LVV>Или паттерн "Стратегия"? LVV>Они ж как раз для таких случаев и предназначены — для разруливания большого переключателя.
ну так чувак фактически и изобрел свой паттерн "команда", предлагая свое решение задачи.
В клиент-серверных приложениях еще есть куча проблем с обратной совместимостью, порядком байт, обработкой обрывов соединений, склеиванием сообщений и так далее.
Посмотрите в сторону, например, ZeroC Ice. Очень хорошая штука.
Здравствуйте, LaptevVV, Вы писали:
LVV>А просто паттерн "команда" применить разве нельзя? LVV>Или паттерн "Стратегия"? LVV>Они ж как раз для таких случаев и предназначены — для разруливания большого переключателя.
Все верно это и есть паттерн команда
но чтобы этим воспользоваться нужен опять же некоторый признак
и в результате приходим к switch
или вы знаете более другой некий способ использования порлиморфизма в данном случае ?
идея в том чтобы на этапе компиляции была известна вся информация о конкректной команде
и по возможности этот switch генерировал компилятор, я не хочу чтобы у меня в коде присуствовал
switch на десяь экранов даже если он будет в отдельном файле
Здравствуйте, CreatorCray, Вы писали:
CC>Здравствуйте, ononim, Вы писали:
O>>CommandHandler command_handlers[MAX_COMMAND_ID] — будет даже быстрее чем switch CC>В общем случае не будет. CC>switch давно уже компиляторами раскладывается в jmp по адресу из таблицы, если значения меток подходят для создания такой таблицы.
Угу, а в том месте, куда происходит jmp производится вызов нужного обработчика. Ну разве что обработчики тривиальные и заинлайнены в теле switch.
Здравствуйте, sokel, Вы писали:
S>Угу, а в том месте, куда происходит jmp производится вызов нужного обработчика. Ну разве что обработчики тривиальные и заинлайнены в теле switch.
Что мешает заоптимизить jmp сразу на обработчик?
Здравствуйте, CreatorCray, Вы писали:
CC>Здравствуйте, sokel, Вы писали:
S>>Угу, а в том месте, куда происходит jmp производится вызов нужного обработчика. Ну разве что обработчики тривиальные и заинлайнены в теле switch. CC>Что мешает заоптимизить jmp сразу на обработчик?
Для этого компилятор должен сначала выяснить, что все используемые в switch обработчики однородны, то есть имеют одинаковую сигнатуру + в меcте jump вызывается только обработчик. После этого ему нужно будет построить не jmp таблицу, а таблицу обработчиков + реализовать переход по адресу из таблицы с заполнением нужных параметров вызова. Сомневаюсь, что какой либо из существующих компиляторов имеет такой мозг.
I>Каждый раз когда я вижу такой switch — case на несколько экранов приходит непреодолимое желание I>изменить этот код. I>Что хотелось бы, так это избавиться от такой конструкции, заменив это чем нибудь другим. I>Вопрос производительности пока не принципиален. Интересны именно подходы.
Зайдите сюда http://www.dslev.narod.ru/PointersToMembers.htm и прочитайте статью внимательно, может описанный подход Вам поможет. Статья небольшая, в конце статьи написано "Наконец этот подход можно использовать для выполнения любого ряда действий, а не только рисования, фактически можно организовать сценарий из N функций, выполняя их в определенном порядке, который очень просто изменяется."
Здравствуйте, ioni, Вы писали:
I>Мне интересно а как вы решаете проблему большого switch.
Тут много было решений и с макросами и с шаблонами... Но странно, что никто так и не предложил тру-C++ подход. Вобще говоря я так понимаю человека интересует не оптимизации компилятора (врядли обработчик команд является узким местом), а именно дизайн и гибкость кода.
В общем мой вариант без использования шаблонов, макросов, буста и т.п:
class Command
{
public:
virtual ~Command() {}
virtual void Initialize(Parser* context) {}
virtual Command* Clone() const = 0;
virtual void Run() = 0;
};
class SomeCommand : public Command
{
public:
SomeCommand(const SomeCommand &src) {}
void Run()
{
std::cout << "hello";
}
// Эти методы одинаковые для всех комманд
// их можно обернуть в простой макрос.virtual Command* Clone() const { return new SomeCommand(*this); }
virtual void Initialize(Parser& parser)
{
// Вызываем перегруженный метор void Parser::Parse(SomeCommand& command);
// Он аполняет поля комманды если таковые имеются.
parser.Parse(*this);
}
};
const int MAX_COMMANDS = 1;
Command* commands[MAX_COMMANDS];
void main()
{
Parser parser;
commands[0] = new SomeCommand;
Command* cmd = commands[0]->Clone();
cmd->Initialize(parser);
cmd->Run();
delete cmd;
}
auto_ptr и прочее не испольуется намеренно, т.к. я не в курсе о стратегии управления памятью в коде автора. При желании можно добавить и макросов и шаблонов по вкусу. Тут же только суть.
Объяснение: имеем массив экемпляров комманд. При получении комманды клонируем экземпляр по индексу в массиве. Далее вызываем функцию SomeCommand::Initialize которая передает управление в парсер, где происходит разбор параметров комманды. После разбора мы вольны делать с нею что угодно, например сохранить в очередь на выполнение, ну или как в данном случае просто запускаем методом SomeCommand::Run.
Плюс этого подхода в том, что мы отделяем парсер комманд от действия. Таким образом теперь можно например выполнять обработку комманд в другом потоке, независимом от парсера. Код парсера не загромождается ненужными инклудами, которые необходимы для обработки комманды.