简体   繁体   中英

Simple text menu in C++

I am writing a silly little app in C++ to test one of my libraries. I would like the app to display a list of commands to the user, allow the user to type a command, and then execute the action associated with that command. Sounds simple enough. In C# I would end up writing a list/map of commands like so:

    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)));
    }

Any suggestions on how to achieve this in C++? I don't really want to define separate classes/functions for each command. I can use Boost, but not TR1.

A very common technique is to use function pointers, or boost::function, indexed by the item name, or by having a vector of them and indexing by the item index for this job. Simple example using the item name:

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++ doesn't have a lambda feature yet, so you really have to use functions for this task, sadly. You can use boost::lambda, but note it is just simulating lambdas, and nowhere near as powerful as a native solution:

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

Note the use of constant(...), since otherwise boost::lambda wouldn't notice that this is supposed to be a lambda expression: The compiler would try to output the string using std::cout, and assign the result (an std::ostream reference) to menu["help"]. You can still use boost::function, since it will accept everything returning void and taking no arguments - including function objects, which is what boost::lambda creates.

If you really don't want separate functions or boost::lambda, you can just take print out a vector of the item names, and then switch on the item number given by the user. This is probably the easiest and most straight forward way of doing it.

Why not just port the C# code to C++? There's a little work to be done, but something like this should get most of your work done:

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;
}

With that defined, your main() can use a std::list, and use a simple while() loop that checks the exit value of the MenuItem's Action to determine if it should exit.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM