1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
|
#include <iostream>
#include <sstream>
#include <string>
#include <list>
#include <memory>
#define show(variable) std::cout << #variable << " = " << variable << std::endl;
const int NOT_FOUND = -1, END_OF_MENU = -1;
const std::string EMPTY = "", NEW = " ";
template <typename T>
inline std::string toString (const T& t) {
std::ostringstream os;
os << t;
return os.str();
}
template <typename T = int>
inline T ReadType (const std::string& prompt = EMPTY) {
T n;
while ( (std::cout << prompt) && !(std::cin >> n) ) {
std::cout << "You must enter an integer." << std::endl;
std::cin.clear();
std::cin.ignore (1000, '\n');
}
return n;
}
template <typename T>
class Menu {
private:
class Option {
private:
const std::string name;
const T item;
Menu* submenu;
Option* next;
friend class Menu<T>;
Option (const std::string& itemName, const T& t, Menu<T>* menu = nullptr) : name(itemName), item(t), submenu(menu), next(nullptr) {}
~Option() {if (submenu) delete submenu;} // Delete only if submenu != nullptr, else there may be double deletion crashes.
inline T choose() const;
inline void print (int n) const;
};
Option* first = nullptr; // first option in the menu
Menu<T>* parent = nullptr;
Option* parentOption = nullptr; // If this menu is a submenu, parentOption is the Option* whose 'submenu' data member is 'this'.
enum ChoosingType {Normal, Remove};
public:
Menu() = default;
Menu (const Menu<T>&);
Menu& operator = (const Menu<T>&);
~Menu();
inline void insert (const std::string& itemName, const T& t, Menu<T>* submenu = nullptr, int pos = END_OF_MENU);
T choose() const {return chosenItem().first;}
inline int print() const;
private:
inline std::pair<T, int> chosenItem (ChoosingType = Normal) const;
inline Option* deepCopy (const Option*);
};
template <typename T>
Menu<T>::~Menu() {
std::cout << "Menu<T> destructor called\n";
if (!first) return; // Important. Otherwise the menu may be destroyed twice, causing a crash.
Menu::Option *current, *next;
for (current = first; current; current = next) {
next = current->next;
delete current;
}
first = nullptr; // Necessary to prevent a menu from being destroyed twice.
if (parentOption)
parentOption->submenu = nullptr; // Must set parentOption->submenu to nullptr so that this submenu is not deleted twice, causing a crash.
}
template <typename T>
inline void Menu<T>::Option::print (int n) const {
if (!submenu)
std::cout << std::left << toString (n) + ". " + name;
else
std::cout << toString (n) + ". " + name << "-> (submenu)";
std::cout << std::endl;
}
template <typename T>
inline T Menu<T>::Option::choose() const {
if (!submenu)
return item;
std::cout << std::endl << "Submenu entered." << std::endl;
return submenu->choose();
}
template <typename T>
inline void Menu<T>::insert (const std::string& itemName, const T& t, Menu<T>* submenu, int pos) { // submenu = nullptr, pos = END_OF_MENU = -1 by default (pos = 0 is the first position)
Menu::Option *newOption = new Menu::Option (itemName, t, submenu);
if (submenu) submenu->parentOption = newOption;
Menu::Option *current, *prev = nullptr;
int currentPosition = 0;
for (current = first; current && currentPosition++ != pos; current = current->next)
prev = current;
if (!prev) {
newOption->next = first;
first = newOption;
}
else {
newOption->next = current;
prev->next = newOption;
}
}
template <typename T>
inline std::pair<T, int> Menu<T>::chosenItem (ChoosingType) const {
int n, choiceNumber;
Menu::Option *current = first;
while (true) {
n = print();
choiceNumber = ReadType();
if (choiceNumber >= 1 && choiceNumber <= n)
break;
std::cout << std::endl << "You must choose from the above list of choices." << std::endl;
}
n = 1; // we start at n=1 now because the menu numbers begin with 1, so 1 is the first possible value of choice
for (current = first; (n != choiceNumber) && current; current = current->next) // move to the chosen option
++n;
const T choice = current->choose(); // choose the option
return std::make_pair(choice, n);
}
template <typename T>
inline int Menu<T>::print() const {
int n = 0;
Menu::Option *current = first;
for (current = first; current; current = current->next)
current->print (++n);
return n;
}
template <typename T>
Menu<T>::Menu (const Menu<T>& other) { // Copy constructor.
std::cout << "Menu<T> copy constructor called." << std::endl;
if (!other.first) // This check is needed (tested).
first = nullptr;
else
first = deepCopy (other.first);
}
template <typename T>
Menu<T>& Menu<T>::operator = (const Menu<T>& other) { // Assignment operator.
std::cout << "Menu<T> assignment operator called." << std::endl;
if (this == &other)
return *this;
if (!other.first) // This check is needed (tested).
first = nullptr;
else
first = deepCopy (other.first);
return *this;
}
template <typename T>
inline typename Menu<T>::Option* Menu<T>::deepCopy (const typename Menu<T>::Option* other) {
Option* clone = new Option(*other); // clone is a new Option that has all the values of 'other', but its 'next' pointer is unfortunately the same address as other's next. This must be corrected.
Option* corrector = clone;
const auto createClonedSubmenu = [this](Option* o, Menu<T>* submenuFound) {
o->submenu = new Menu<T>;
o->submenu->parentOption = o;
if (submenuFound->first)
o->submenu->first = deepCopy (submenuFound->first);
};
if (clone->submenu)
createClonedSubmenu (corrector, clone->submenu);
for (Option* current = clone->next; current; current = current->next) {
corrector->next = new Option(*current);
if (current->submenu)
createClonedSubmenu (corrector->next, current->submenu);
corrector = corrector->next;
}
corrector = nullptr;
return clone;
}
int main() {
{
Menu<int> newMenu;
{
Menu<int> menu, submenu, subsubmenu1, subsubmenu2, subsubsubmenu;
menu.insert("ChoiceA", 1); menu.insert("ChoiceB", 2); menu.insert("ChoiceC", 3);
submenu.insert("ChoiceD", 4);
subsubmenu1.insert("ChoiceG", 7); subsubmenu1.insert("ChoiceH", 8); subsubmenu1.insert("ChoiceI", 9);
submenu.insert ("SubSubmenu1", NOT_FOUND, &subsubmenu1);
submenu.insert("ChoiceE", 5); submenu.insert("ChoiceF", 6);
subsubsubmenu.insert("ChoiceM", 13); subsubsubmenu.insert("ChoiceN", 14); subsubsubmenu.insert("ChoiceO", 15);
subsubmenu2.insert("ChoiceJ", 10); subsubmenu2.insert("ChoiceK", 11); subsubmenu2.insert("ChoiceL", 12);
subsubmenu2.insert("SubSubSubmenu", NOT_FOUND, &subsubsubmenu);
submenu.insert ("SubSubmenu2", NOT_FOUND, &subsubmenu2);
menu.insert ("Submenu", NOT_FOUND, &submenu);
newMenu = menu; // Calls assignment operator.
} // menu, submenu, subsubmenu1, subsubmenu2, subsubsubmenu are all destroyed at this point, but thanks to the deep copying, there is no crash in the upcoming lines.
const int choice = newMenu.choose();
std::cout << "You chose choice #" << choice << ".\n\n\n";
Menu<int>* temp = new Menu<int>; *temp = newMenu;
Menu<int> menuFromCopyConstructor = *temp; // Calls copy constructor.
delete temp;
const int choice2 = menuFromCopyConstructor.choose(); // This still works fine even though 'temp' has been deleted, thanks to the deep copying.
std::cout << "You chose choice #" << choice2 << ".\n";
}
std::cout << "End of test." << std::endl;
std::cin.get(); std::cin.get();
}
|