Making a menu in C++ (OOP)

Viewed 1428

I'm creating a console application and have the need for a menu. This menu can link to a next menu or preform an action. What is a good OOP way of doing this?

I've seen many do it like this, but isn't really maintainable

TJ::clearScreen(); // Function I wrote to clear the console
TJ::breakSection('='); // Creates a line of "===...==="
std::cout << "Main menu" << std::endl;
TJ::breakSection();
std::cout << "[1] abcd" << std::endl;
std::cout << "[2] hrhdd" << std::endl;
std::cout << "[3] aeshsrh" << std::endl;
TJ::breakSection('=');

std::cin >> choice;

switch(choice) {
    case 1:
        doThing1();
        break;
    case 2:
        doThing1();
        break;
    case 3:
        doThing1();
        break;
}

Is there a way to make it look like this?

menu.addChoice(functionA);
menu.addChoice(menuB);
menu.addChoice(functionC);
menu.addChoice(menuD);
menu.display();

So if the user chooses option A (by typing "A" into the console), it will execute functionA. If the user chooses option B, it will show menuB. I've looked at doing something like you see below. But I can't figure out how to allow both functions and menus to be chosen.

class Menu {
private:
    std::string title;

    class Item {
    private:

    public:
        Item();
        ~Item();

        int run();
        int display();
    };

    std::vector<Item> Choices;
public:
    Menu();
    ~Menu();

    Menu& addChoice();
    Menu& display();
};
2 Answers

It looks like you are looking for function pointers. You can store them in a vector like so:

std::vector<void (*) ()> options{&functionA , &displayMenuB, &functionC, &displayMenuD}; 
options[0](); // Executes function A
options[1](); // Shows menu B

The only issue is that displayMenuB needs to be a function here, not a piece of raw data... ( I did not completely understand your problem, but if you just want to display menu B to the console it could be a function returning a void).

Of course you need to wrap all of that in a class and properly encapsulate it, but I hope you get the idea.

Note: Function pointers can only point to stateless function. If you want to store a functors ( basically an object with some data but which can be called like a function ), you should use std::function.

I've put some efforts and created a kind of menu infrastructure for your problem. It can store functions with the same parameter list. Also, my implementation uses std::map, so it is more convenient and faster than std::vector indexing.
Menu.h

#pragma once

#include <functional>
#include <string>
#include <map>

using std::function;
using std::string;
using std::map;

template <class ...Types>
class Menu
{
private:
    template <class ...Types>
    class Item
    {
    private:
        function<void(Types...)> func;
        string desc;
    public:
        Item() = default;
        Item(function<void(Types...)> func, string desc) : func(func), desc(desc) {}
        void setDescription(string desc) { this->desc = desc; }
        string getDescription() { return desc; }
        void run(Types ... params) { func(std::forward<Types>(params)...); }
    };
private:
    string text;
    map<string, Item<Types...>> choices;
public:
    void addChoice(string key, function<void(Types...)> func, string desc = "");
    void setText(string txt) { text = txt; }
    void setDescription(string key, string desc);
    void run(string key, Types ... params);
    void display();
};

Menu.cpp

#include "Menu.h"

#include <iostream>

using std::cout;
using std::endl;

template<class ...Types>
void Menu<Types...>::addChoice(string key, function<void(Types...)> func, string desc)
{
    Item<Types...> item{ func, desc };
    this->choices[key] = item;
}

template<class ...Types>
void Menu<Types...>::setDescription(string key, string desc)
{
    this->choices[key].setDescription(desc);
}

template<class ...Types>
void Menu<Types...>::run(string key, Types ... params)
{
    if (choices.find(key) != choices.end())
        this->choices[key].run(std::forward<Types>(params)...);
}

template<class ...Types>
void Menu<Types...>::display()
{
    cout << this->text << endl;
    for (auto& c : this->choices)
        cout << "\t" << c.first << ":  " << c.second.getDescription() << endl;
    cout << endl;
}

template class Menu<>;

Main.cpp

#include <iostream>

#include "Menu.h"

using std::cout;
using std::cin;
using std::endl;

void funcA();
void funcB();
void funcC();

int main()
{
    Menu<> menu;
    menu.setText("Choose:");
    menu.addChoice("A", funcA, "A description");
    menu.addChoice("B", funcB /*no description*/);
    menu.addChoice("C", funcC /*no description*/);
    menu.setDescription("C", "C description");
    menu.display();

    string s;
    std::getline(cin, s);
    menu.run(s);

    return 0;
}

void funcA()
{
    cout << "Function A" << endl;
}

void funcB()
{
    cout << "Function B" << endl;
}

void funcC()
{
    cout << "Function C" << endl;
}

Output:

Choose:
    A:  Function A
    B:
    C:  Function C
A
Function A

Don't forget about putting template class Menu<*types*>; for any other signature.

Related