Простое текстовое меню в C++

Я пишу глупое маленькое приложение на C++ для тестирования одной из моих библиотек. Я хотел бы, чтобы приложение отображало список команд для пользователя, позволяло пользователю вводить команду, а затем выполнять действие, связанное с этой командой. Звучит достаточно просто. В С# я бы написал список/карту команд следующим образом:

    class MenuItem
    {
        public MenuItem(string cmd, string desc, Action action)
        {
            Command = cmd;
            Description = desc;
            Action = action;
        }

        public string Command { get; private set; }
        public string Description { get; private set; }
        public Action Action { get; private set; }
    }

    static void Main(string[] args)
    {
        var items = new List<MenuItem>();

        items.Add(new MenuItem(
            "add",
            "Adds 1 and 2",
            ()=> Console.WriteLine(1+2)));
    }

Любые предложения о том, как добиться этого на С++? Я действительно не хочу определять отдельные классы/функции для каждой команды. Я могу использовать Boost, но не TR1.


person Filip Frącz    schedule 14.11.2008    source источник


Ответы (2)


Очень распространенный метод заключается в использовании указателей на функции или boost::function, индексированных по имени элемента, или с помощью их вектора и индексации по индексу элемента для этого задания. Простой пример с использованием имени элемента:

void exit_me(); /* exits the program */
void help(); /* displays help */

std::map< std::string, boost::function<void()> > menu;
menu["exit"] = &exit_me;
menu["help"] = &help;

std::string choice;
for(;;) {
    std::cout << "Please choose: \n";
    std::map<std::string, boost::function<void()> >::iterator it = menu.begin();
    while(it != menu.end()) {
        std::cout << (it++)->first << std::endl;
    }

    std::cin >> choice;
    if(menu.find(choice) == menu.end()) {
        /* item isn't found */
        continue; /* next round */
    }   

    menu[choice](); /* executes the function */
}

C++ еще не имеет лямбда-функции, поэтому, к сожалению, вам действительно придется использовать функции для этой задачи. Вы можете использовать boost::lambda, но обратите внимание, что это просто имитация лямбда-выражений, и далеко не так мощно, как нативное решение:

menu["help"] = cout << constant("This is my little program, you can use it really nicely");

Обратите внимание на использование константы(...), поскольку в противном случае boost::lambda не заметил бы, что это должно быть лямбда-выражение: компилятор попытался бы вывести строку, используя std::cout, и присвоить результат ( ссылка на std::ostream) на menu["help"]. Вы по-прежнему можете использовать boost::function, так как он примет все, что возвращает void и не принимает аргументов, включая объекты функций, которые создает boost::lambda.

Если вам действительно не нужны отдельные функции или boost::lambda, вы можете просто распечатать вектор имен элементов, а затем switch на номере элемента, указанном пользователем. Это, пожалуй, самый простой и прямой способ сделать это.

person Johannes Schaub - litb    schedule 14.11.2008
comment
Близко, но мне все равно придется определить отдельные функции exit_me() и help()... Есть ли другой способ? - person Filip Frącz; 14.11.2008
comment
Спасибо. Я буду придерживаться использования отдельных функций и дождусь включения лямбда-выражений TR1 в стандарт. - person Filip Frącz; 14.11.2008
comment
лямбда-выражения станут новой языковой функцией следующего С++. вы сможете сделать menu[help] = []() { cout ‹‹ это моя небольшая помощь; }; тогда. - person Johannes Schaub - litb; 14.11.2008
comment
Небольшая ошибка, должно быть std::cout ‹‹ (*it++).first ‹‹ std::endl; если вы не перегрузили ‹‹ для этой карты. Во всяком случае, аккуратный кусок кода. - person Nazgob; 15.11.2008

Почему бы просто не перенести код C# на C++? Предстоит проделать небольшую работу, но что-то вроде этого должно выполнить большую часть вашей работы:

using std::string;
class MenuItem    
{        
    public:
        MenuItem(string cmd, string desc, boost::function<bool()> action):Command(cmd),
                                                                          Description(desc),
                                                                          Action(action) 
        {}
        boost::function<bool()> GetAction() { return Action; }
        string GetDescription() { return Description; }
        string GetCommand() { return Command; }
    private:
        string Command;
        string Description;
        boost::function<bool()> Action;
}

С этим определением ваш main() может использовать std::list и использовать простой цикл while(), который проверяет значение выхода действия MenuItem, чтобы определить, должен ли он выйти.

person Harper Shelby    schedule 14.11.2008
comment
MenuItem здесь не проблема. Это отсутствие лямбд или замыканий. В современном C++ мне приходится определять отдельные функции для каждой моей команды (boost::function будет указывать на них), вместо того, чтобы делать все это в одном месте. См. комментарии litb. - person Filip Frącz; 14.11.2008