Поделитесь идеями... :-)
От: ioni Россия  
Дата: 30.12.09 18:35
Оценка:
Довольно часто, по крайней мере у меня в проектах, есть некоторый протокол через который приложение
общается с внешним миром. Протокол состоит из команд, которых может быть масса.
Соответсвенно в программе есть немерянный 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 на несколько экранов приходит непреодолимое желание
изменить этот код.
Что хотелось бы, так это избавиться от такой конструкции, заменив это чем нибудь другим.
Вопрос производительности пока не принципиален. Интересны именно подходы.

Мой набросок выглядит примерно так ( пока нигде не применялся )

struct command_base_
{
    virtual long id() const = 0;
    virtual void execute() = 0;
};
//
template < long id, typename F >
struct command_id_ : command_base_
{
    long id() const {   return id; }
    void execute()  { ... }
};
//
typedef command_id_ < 0, boost::function<...> > command_id_0;
typedef command_id_ < 1, boost::function<...> > command_id_1;
//
command_base_* cmds[] = 
{
   new command_id_0(...),
   new command_id_1(...),
   ...
};
const int sz_cmds = sizeof(cmds)/ sizeof(command_base_);
//
void process_command( long cmd_id )
{
    //find command
    command_base* c = std::lower_bound(cmds, cmds + sz_cmds, make_command(id), command_lesser() );
    // execute command
    c->execute(...);
}


некоторый детали пропущены, но подход думаю что должен быть ясен.

Мне интересно а как вы решаете проблему большого switch.
Re: Поделитесь идеями... :-)
От: nen777w  
Дата: 30.12.09 18:50
Оценка: +1
switch — конструкции, в особенности если cmd_1....cmd_N имеют линейный порядок очень не плохо оптимизирует сам компилятор.
Думаю что Вы это хорошо знаете.
В одной кнторе в которой Я работал, был такой бооольшой свитч по внутренним состояниям приложения... сперва думали переписать это
, потом стало ясно что не будем это делать, поотому что время на это нужно было затратить массу да и компилятор не плохо справлялся тут без нас.
Re: Поделитесь идеями... :-)
От: Sni4ok  
Дата: 30.12.09 18:59
Оценка:
Здравствуйте, ioni, Вы писали:


I>
I>typedef command_id_ < 0, boost::function<...> > command_id_0;
I>typedef command_id_ < 1, boost::function<...> > command_id_1;

I>



а просто std::map<long, boost::function<...> > не пойдёт?
Re[2]: Поделитесь идеями... :-)
От: ioni Россия  
Дата: 30.12.09 19:09
Оценка:
Здравствуйте, Sni4ok, Вы писали:

S>а просто std::map<long, boost::function<...> > не пойдёт?


Наверное пойдет как вариант...описанная проблема это только часть задачи.
Каждая команда при выполнении может принимать данные и может возвращать ответы, вообщем случае разные.

typedef command < id > command_id_type
typedef boost::tuple< response_id_1, ... > response_type_list;
typedef ...< ... > command_data_type;
typedef command< command_id_type, response_type_list, command_data_type, function > command_N;


Хотелось бы заставить компилятор по максиму генерировать код. Пока не знаю что из этого получиться.
Re[2]: Поделитесь идеями... :-)
От: ioni Россия  
Дата: 30.12.09 19:11
Оценка:
Здравствуйте, nen777w, Вы писали:

N>switch — конструкции, в особенности если cmd_1....cmd_N имеют линейный порядок очень не плохо оптимизирует сам компилятор.

N>Думаю что Вы это хорошо знаете.
N>В одной кнторе в которой Я работал, был такой бооольшой свитч по внутренним состояниям приложения... сперва думали переписать это
N>, потом стало ясно что не будем это делать, поотому что время на это нужно было затратить массу да и компилятор не плохо справлялся тут без нас.

Вот вот... унаследованый код конечно никто трогать не собирается
Но на будущее хотелось бы иметь несколько возможных вариантов решения
Re: Поделитесь идеями... :-)
От: Alexander G Украина  
Дата: 30.12.09 19:11
Оценка:
Здравствуйте, ioni, Вы писали:


I>command_base_* cmds[] =

I>{
I> new command_id_0(...),
I> new command_id_1(...),
I> ...
I>};

Глобальный список?
remark интересную штуку на этот случай предлагал
Автор: remark
Дата: 03.09.08


I>Мне интересно а как вы решаете проблему большого switch.


MFC/WTL-евский MESSAGE_MAP — первое, что на ум приходит, только он не менее мерзок.

Можно сделать switch выглядящим красивее:
enum Command { Cmd1, Cmd2, Cmd3 }

template<Command > void Handler(...);

template<> void Handler<Cmd1> Handler(...) { ... }
template<> void Handler<Cmd2> Handler(...) { ... }
template<> void Handler<Cmd3> Handler(...) { ... }

void HandleMessage()
{
#pragma warning (error: 4062)  // ([url=http://rsdn.ru/?forum/cpp/2263231.aspx]отсюда[/url])
  switch (cmd)
  {
  case Cmd1: return Handler<Cmd1>(...);
  case Cmd2: return Handler<Cmd2>(...);
  case Cmd3: return Handler<Cmd3>(...);
  }
  ASSERT(!!"Invalid command");
}
Русский военный корабль идёт ко дну!
Re[2]: Поделитесь идеями... :-)
От: Аноним  
Дата: 30.12.09 19:15
Оценка:
Здравствуйте, nen777w, Вы писали:

N>switch — конструкции, в особенности если cmd_1....cmd_N имеют линейный порядок очень не плохо оптимизирует сам компилятор.

ИМХО константный, давно правда дисасемблер не смотрел
Re: Поделитесь идеями... :-)
От: ankorol Украина  
Дата: 03.01.10 12:00
Оценка:
Здравствуйте, ioni, Вы писали:

I>Довольно часто, по крайней мере у меня в проектах, есть некоторый протокол через который приложение

I>общается с внешним миром. Протокол состоит из команд, которых может быть масса.
I>Соответсвенно в программе есть немерянный switch()... на каждую команду соответственно.
Предлагаю свой способ:
struct NullType{};

template<class Head,class Tail>
struct TypeList
{
    typedef Head head;
    typedef Tail tail;
};

template<int MsgID,typename F>
class MsgFuncPair
{
public:
    F funct;
    enum{value = MsgID};
    void exec(){funct();};
};

template<class TList>
class Switcher
{
    typename TList::head hd;
    Switcher<typename TList::tail> tl;
public:
    void DoThis(int Msg)
    {
        if(Msg==TList::head::value)        
        {
            return hd.exec();
        }
        return tl.DoThis(Msg);
    }
};

template<>
class Switcher<NullType>
{
public:
    static void DoThis(int Msg)
    {
        std::cout<<"Not found!"<<std::endl;
        return ;
    }
};

Использование:
class Funct1
{
public:
    void operator()()
    {
        std::cout<<"Func1"<<std::endl;    
    }
};

class Funct2
{
public:
    void operator()()
    {
        std::cout<<"Func2"<<std::endl;    
    }
};

class Funct3
{
public:
    void operator()()
    {
        std::cout<<"Func3"<<std::endl;    
    }
};

int main(int argc, char* argv[])
{
    Switcher<TypeList<MsgFuncPair<1,Funct1>,TypeList<MsgFuncPair<3,Funct3>,NullType> > > swit;
    swit.DoThis(1);
    swit.DoThis(2);
    swit.DoThis(3);
    return 0;
}

Вывод:
Func1
Not found!
Func3

Это, конечно, набросок, надо прикрутить к этому boost::function и можно будет использовать.
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Re: Поделитесь идеями... :-)
От: ononim  
Дата: 04.01.10 11:22
Оценка:
CommandHandler command_handlers[MAX_COMMAND_ID] — будет даже быстрее чем switch
Ну или std::map если производительность в самом деле совсем пофиг
Если хочется декларативности то можно извратиться примерно так:
#define MAX_COMMAND_ID 0x10
typedef void (__stdcall *CommandHandler)();
CommandHandler command_handlers[MAX_COMMAND_ID] = {0};

#define COMMAND_HANDLER(cmdid) \
    void __stdcall CommandHandler##cmdid(); \
    bool CommandHandlerInit##cmdid(){command_handlers[cmdid] = CommandHandler##cmdid; return 1;} \
    static bool s_CommandHandlerFlag##cmdid = CommandHandlerInit##cmdid(); \
    void __stdcall CommandHandler##cmdid()

#define INVOKE_HANDLER(cmdid) command_handlers[cmdid]();

COMMAND_HANDLER(1)
{
    printf("1\n");
}

COMMAND_HANDLER(2)
{
    printf("2\n");
}

COMMAND_HANDLER(3)
{
    printf("3\n");
}

void wmain(int argc, wchar_t **argv)
{
    INVOKE_HANDLER(1);
    INVOKE_HANDLER(2);
    INVOKE_HANDLER(3);
}


ЗЫ не бейте меня
Как много веселых ребят, и все делают велосипед...
Re[2]: Поделитесь идеями... :-)
От: CreatorCray  
Дата: 04.01.10 11:46
Оценка: 6 (1) +1
Здравствуйте, ononim, Вы писали:

O>CommandHandler command_handlers[MAX_COMMAND_ID] — будет даже быстрее чем switch

В общем случае не будет.
switch давно уже компиляторами раскладывается в jmp по адресу из таблицы, если значения меток подходят для создания такой таблицы.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re: Поделитесь идеями... :-)
От: LaptevVV Россия  
Дата: 04.01.10 12:21
Оценка:
Здравствуйте, 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>


А просто паттерн "команда" применить разве нельзя?
Или паттерн "Стратегия"?
Они ж как раз для таких случаев и предназначены — для разруливания большого переключателя.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[2]: Поделитесь идеями... :-)
От: игппук Беларусь  
Дата: 04.01.10 13:50
Оценка:
Здравствуйте, LaptevVV, Вы писали:

LVV>А просто паттерн "команда" применить разве нельзя?

LVV>Или паттерн "Стратегия"?
LVV>Они ж как раз для таких случаев и предназначены — для разруливания большого переключателя.

ну так чувак фактически и изобрел свой паттерн "команда", предлагая свое решение задачи.
проклятый антисутенерский закон
Re: Поделитесь идеями... :-)
От: Ulitka США http://lazarenko.me
Дата: 08.01.10 21:15
Оценка:
В клиент-серверных приложениях еще есть куча проблем с обратной совместимостью, порядком байт, обработкой обрывов соединений, склеиванием сообщений и так далее.
Посмотрите в сторону, например, ZeroC Ice. Очень хорошая штука.
Re[2]: Поделитесь идеями... :-)
От: ioni Россия  
Дата: 11.01.10 05:40
Оценка:
Здравствуйте, LaptevVV, Вы писали:

LVV>А просто паттерн "команда" применить разве нельзя?

LVV>Или паттерн "Стратегия"?
LVV>Они ж как раз для таких случаев и предназначены — для разруливания большого переключателя.

Все верно это и есть паттерн команда
но чтобы этим воспользоваться нужен опять же некоторый признак

void process_command( long cmd_id, .... )
{
    // find command: command_base* c = ...(cmd_id) ;
    // execute command: c->execute(...);
    // find and execute command
    // global_command_table[ cmd_id ]->execute( ... );
}


и в результате приходим к switch
или вы знаете более другой некий способ использования порлиморфизма в данном случае ?
идея в том чтобы на этапе компиляции была известна вся информация о конкректной команде
и по возможности этот switch генерировал компилятор, я не хочу чтобы у меня в коде присуствовал
switch на десяь экранов даже если он будет в отдельном файле
Re[3]: Поделитесь идеями... :-)
От: sokel Россия  
Дата: 11.01.10 07:41
Оценка:
Здравствуйте, CreatorCray, Вы писали:

CC>Здравствуйте, ononim, Вы писали:


O>>CommandHandler command_handlers[MAX_COMMAND_ID] — будет даже быстрее чем switch

CC>В общем случае не будет.
CC>switch давно уже компиляторами раскладывается в jmp по адресу из таблицы, если значения меток подходят для создания такой таблицы.

Угу, а в том месте, куда происходит jmp производится вызов нужного обработчика. Ну разве что обработчики тривиальные и заинлайнены в теле switch.
Re[2]: Поделитесь идеями... :-)
От: sokel Россия  
Дата: 11.01.10 08:31
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>remark интересную штуку на этот случай предлагал
Автор: remark
Дата: 03.09.08


можно добавить CRTP и сделать чуть извращённей:
template<typename impl_type, unsigned int max_cmd_id> 
class cmd_handler_base : class_initializer<cmd_handler_base<impl_type,max_cmd_id> >
{
    typedef void (impl_type::*cmd_handler_type) (void);
    template<unsigned int cmd_id = 0> 
    struct fill_cmd_handler
    {
        fill_cmd_handler() { cmd_handler_base::cmd_handlers[cmd_id] = &impl_type::on_command<cmd_id>; }
        fill_cmd_handler<cmd_id+1> fill_next_handler;
    };
    template<> struct fill_cmd_handler<max_cmd_id+1> {};
    static cmd_handler_type cmd_handlers[max_cmd_id+1];
public:
    static void static_ctor() { fill_cmd_handler<0>(); } // заполняем таблицу обработчиков
    void process_command(unsigned int cmd)
    {
        if(cmd > max_cmd_id) static_cast<impl_type&>(*this).on_unknown_command(cmd);
        else
        {
            cmd_handler_type handler = cmd_handlers[cmd];
            (static_cast<impl_type&>(*this).*handler)();
        }
    }
};
template<typename impl_type, unsigned int max_cmd_id> typename cmd_handler_base<impl_type, max_cmd_id>::cmd_handler_type
cmd_handler_base<impl_type, max_cmd_id>::cmd_handlers [max_cmd_id+1];


Использование:
struct my_cmd_handler : cmd_handler_base<my_cmd_handler, 2>
{
    // тут уже можно использовать специализацию
    template<unsigned int cmd_id> void on_command() { printf("process command: %u\n", cmd_id); }
    void on_unknown_command(unsigned int cmd_id) { printf("unknown command: %u\n", cmd_id); }
};
int main()
{
    my_cmd_handler h;
    h.process_command(0);
    h.process_command(1);
    h.process_command(2);
    h.process_command(33);
}
Re[4]: Поделитесь идеями... :-)
От: CreatorCray  
Дата: 11.01.10 08:47
Оценка:
Здравствуйте, sokel, Вы писали:

S>Угу, а в том месте, куда происходит jmp производится вызов нужного обработчика. Ну разве что обработчики тривиальные и заинлайнены в теле switch.

Что мешает заоптимизить jmp сразу на обработчик?
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[5]: Поделитесь идеями... :-)
От: sokel Россия  
Дата: 11.01.10 08:53
Оценка:
Здравствуйте, CreatorCray, Вы писали:

CC>Здравствуйте, sokel, Вы писали:


S>>Угу, а в том месте, куда происходит jmp производится вызов нужного обработчика. Ну разве что обработчики тривиальные и заинлайнены в теле switch.

CC>Что мешает заоптимизить jmp сразу на обработчик?

Для этого компилятор должен сначала выяснить, что все используемые в switch обработчики однородны, то есть имеют одинаковую сигнатуру + в меcте jump вызывается только обработчик. После этого ему нужно будет построить не jmp таблицу, а таблицу обработчиков + реализовать переход по адресу из таблицы с заполнением нужных параметров вызова. Сомневаюсь, что какой либо из существующих компиляторов имеет такой мозг.
Re[3]: Поделитесь идеями... :-)
От: sokel Россия  
Дата: 11.01.10 10:32
Оценка: 4 (1)
Здравствуйте, sokel, Вы писали:

S>Здравствуйте, Alexander G, Вы писали:


AG>>remark интересную штуку на этот случай предлагал
Автор: remark
Дата: 03.09.08


а можно в виде статических функций:
template<typename impl_type, unsigned int max_cmd, typename arg_type> 
class cmd_handler : class_initializer<cmd_handler<impl_type,max_cmd,arg_type> >
{
    typedef void (*cmd_handler_type) (arg_type);
    static cmd_handler_type cmd_handlers[max_cmd+1];
    template<unsigned int cmd> struct fill_cmd_handler
    {
        fill_cmd_handler() { cmd_handler::cmd_handlers[cmd] = &impl_type::on_command<cmd>; }
        fill_cmd_handler<cmd+1> fill_next_handler;
    };
    template<> struct fill_cmd_handler<max_cmd+1> {};
public:
    static void static_ctor() { fill_cmd_handler<0>(); }
    static void process_command(unsigned int cmd, arg_type arg)
    {
        if(cmd > max_cmd) impl_type::on_unknown_command(cmd, arg);
        else cmd_handlers[cmd](arg);
    }
};
template<typename impl_type, unsigned int max_cmd, typename arg_type> typename cmd_handler<impl_type, max_cmd, arg_type>::cmd_handler_type 
cmd_handler<impl_type, max_cmd, arg_type>::cmd_handlers [max_cmd+1];

// использование:
struct my_cmd_handler : cmd_handler<my_cmd_handler, 2, const char*>
{
    template<unsigned int> static void on_command(const char*);
    static void on_unknown_command(unsigned int, const char*);
};
template<> void my_cmd_handler::on_command<0>(const char* arg) { printf("process: 0, arg: %s\n", arg); }
template<> void my_cmd_handler::on_command<1>(const char* arg) { printf("process: 1, arg: %s\n", arg); }
template<> void my_cmd_handler::on_command<2>(const char* arg) { printf("process: 2, arg: %s\n", arg); }
void my_cmd_handler::on_unknown_command(unsigned int cmd, const char* arg) { printf("unknown: %u, arg: %s\n", cmd, arg); }

int main()
{
    my_cmd_handler::process_command(0, "cmd0");
    my_cmd_handler::process_command(1, "cmd1");
    my_cmd_handler::process_command(2, "cmd2");
    my_cmd_handler::process_command(33, "cmd33");
}
Re[4]: Поделитесь идеями... :-)
От: sokel Россия  
Дата: 11.01.10 11:36
Оценка:
Здравствуйте, sokel, Вы писали:

хотя сам бы я оставил switch или массив обработчиков явный с типизацией команд, что-нить вроде этого:
#include <stdio.h>

namespace commands { enum value { command_0, command_1, command_2, max_cmd }; }

template<unsigned int cmd> struct command
{
    const char* arg;
    command(const char* arg) : arg(arg) {}
};
// различные типы команд
typedef command<commands::command_0> command_0;
typedef command<commands::command_1> command_1;
typedef command<commands::command_2> command_2;

struct my_cmd_handler
{
    template<typename cmd_type> 
    // формирование команды с разбором аргументов (или декодирование пакета для сетевого интерфейса)
    void on_command(const char* arg) { cmd_type cmd(arg); on_command(cmd); }
    // непосредственно обработчики
    void on_command(const command_0& cmd);
    void on_command(const command_1& cmd);
    void on_command(const command_2& cmd);
    void on_unknown_command(unsigned int cmd, const char* arg);
    void process_command(unsigned int cmd, const char* arg)
    {
        typedef void (my_cmd_handler::*handler_type)(const char*);
        static handler_type handlers[commands::max_cmd] = 
        { 
            &my_cmd_handler::on_command<command_0>, 
            &my_cmd_handler::on_command<command_1>, 
            &my_cmd_handler::on_command<command_2> 
        };
        if(cmd < commands::max_cmd) (this->*(handlers[cmd]))(arg);
        else on_unknown_command(cmd, arg);
    }
};
void my_cmd_handler::on_command(const command_0& cmd) { printf("process: 0, arg: %s\n", cmd.arg); }
void my_cmd_handler::on_command(const command_1& cmd) { printf("process: 1, arg: %s\n", cmd.arg); }
void my_cmd_handler::on_command(const command_2& cmd) { printf("process: 2, arg: %s\n", cmd.arg); }
void my_cmd_handler::on_unknown_command(unsigned int cmd, const char* arg) { printf("unknown: %u, arg: %s\n", cmd, arg); }

int main()
{
    my_cmd_handler h;
    h.process_command(0, "cmd0");
    h.process_command(1, "cmd1");
    h.process_command(2, "cmd2");
    h.process_command(33, "cmd33");
}
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.