Pembuat menu konsol generik hanya header
Beberapa waktu yang lalu, saya menulis jawaban ini untuk pertanyaan tentang membuat menu baris perintah. Saya merujuknya baru - baru ini dan memperhatikan beberapa hal yang ingin saya perbaiki.
Tujuan
Seperti versi aslinya, tujuannya adalah untuk memiliki kelas yang menyederhanakan konstruksi dan penggunaan menu baris perintah (konsol).
Perbaikan yang saya lakukan adalah:
- mengizinkan salah satu
std::string
ataustd::wstring
petunjuk dan jawaban - memungkinkan pengguna untuk memisahkan pemilih dari deskripsi
- pindahkan semuanya ke dalam modul khusus header
- memungkinkan pembuatan
const
menu - urutkan berdasarkan pemilih
Pertanyaan
Beberapa hal yang saya pertanyakan adalah:
- nama parameter template - apakah bisa ditingkatkan?
- penggunaan
default_in
dandefault_out
- apakah lebih baik menyimpulkan default dari tipe string? - pilihan
std::function<void()>
sebagai operasi untuk setiap pilihan - penggunaan
std::pair
vs. objek khusus - haruskah saya membungkus semua ini dalam namespace?
- apakah ada fungsi yang hilang?
- apakah ada cara untuk membuat
constexpr
versi?
menu.h
#ifndef MENU_H
#define MENU_H
#include <functional>
#include <iostream>
#include <map>
#include <string>
#include <utility>
template <typename T> struct default_in;
template<> struct default_in<std::istream> {
static std::istream& value() { return std::cin; }
};
template<> struct default_in<std::wistream> {
static std::wistream& value() { return std::wcin; }
};
template <typename T> struct default_out;
template<> struct default_out<std::ostream> {
static std::ostream& value() { return std::cout; }
};
template<> struct default_out<std::wostream> {
static std::wostream& value() { return std::wcout; }
};
template <class str, class intype, class outtype>
class ConsoleMenu {
public:
ConsoleMenu(const str& message,
const str& invalidChoiceMessage,
const str& prompt,
const str& delimiter,
const std::map<str, std::pair<str, std::function<void()>>>& commandsByChoice,
intype &in = default_in<intype>::value(),
outtype &out = default_out<outtype>::value());
void operator()() const;
private:
outtype& showPrompt() const;
str message;
str invalidChoiceMessage_;
str prompt;
str delimiter;
std::map<str, std::pair<str, std::function<void()>>> commandsByChoice_;
intype ∈
outtype &out;
};
template <class str, class intype, class outtype>
ConsoleMenu<str, intype, outtype>::ConsoleMenu(const str& message,
const str& invalidChoiceMessage,
const str& prompt,
const str& delimiter,
const std::map<str, std::pair<str, std::function<void()>>>& commandsByChoice,
intype &in, outtype& out) :
message{message},
invalidChoiceMessage_{invalidChoiceMessage},
prompt{prompt},
delimiter{delimiter},
commandsByChoice_{commandsByChoice},
in{in},
out{out}
{}
template <class str, class intype, class outtype>
outtype& ConsoleMenu<str, intype, outtype>::showPrompt() const {
out << message;
for (const auto &commandByChoice : commandsByChoice_) {
out << commandByChoice.first
<< delimiter
<< commandByChoice.second.first
<< '\n';
}
return out << prompt;
}
template <class str, class intype, class outtype>
void ConsoleMenu<str, intype, outtype>::operator()() const {
str userChoice;
const auto bad{commandsByChoice_.cend()};
auto result{bad};
out << '\n';
while (showPrompt() && (!(std::getline(in, userChoice)) ||
((result = commandsByChoice_.find(userChoice)) == bad))) {
out << '\n' << invalidChoiceMessage_;
}
result->second.second();
}
#endif // MENU_H
main.cpp
#include "menu.h"
#include <iostream>
#include <functional>
template <class str, class outtype>
class Silly {
public:
void say(str msg) {
default_out<outtype>::value() << msg << "!\n";
}
};
using MySilly = Silly<std::string, std::ostream>;
int main() {
bool running{true};
MySilly thing;
auto blabble{std::bind(&MySilly::say, thing, "BLABBLE")};
const ConsoleMenu<std::string, std::istream, std::ostream> menu{
"What should the program do?\n",
"That is not a valid choice.\n",
"> ",
". ",
{
{ "1", {"bleep", []{ std::cout << "BLEEP!\n"; }}},
{ "2", {"blip", [&thing]{ thing.say("BLIP"); }}},
{ "3", {"blorp", std::bind(&MySilly::say, thing, "BLORP")}},
{ "4", {"blabble", blabble }},
{ "5", {"speak Chinese", []{std::cout << "对不起,我不能那样做\n"; }}},
{ "0", {"quit", [&running]{ running = false; }}},
}
};
while (running) {
menu();
}
}
Ini menunjukkan penggunaan program dan beberapa cara berbeda untuk membuat fungsi menu. Bergantung pada konsol dan pengaturan compiler Anda, kalimat bahasa Mandarin mungkin ditampilkan atau tidak dengan benar. Berikutnya adalah versi string lebar.
wide.cpp
#include "menu.h"
#include <iostream>
#include <functional>
#include <locale>
template <class str, class outtype>
class Silly {
public:
void say(str msg) {
default_out<outtype>::value() << msg << "!\n";
}
};
using MySilly = Silly<std::wstring, std::wostream>;
int main() {
bool running{true};
MySilly thing;
auto blabble{std::bind(&MySilly::say, thing, L"BLABBLE")};
ConsoleMenu<std::wstring, std::wistream, std::wostream> menu{
L"What should the program do?\n",
L"That is not a valid choice.\n",
L"> ",
L". ",
{
{ L"1", {L"bleep", []{ std::wcout << L"BLEEP!\n"; }}},
{ L"2", {L"blip", [&thing]{ thing.say(L"BLIP"); }}},
{ L"3", {L"blorp", std::bind(&MySilly::say, thing, L"BLORP")}},
{ L"4", {L"blabble", blabble }},
{ L"5", {L"说中文", []{std::wcout << L"对不起,我不能那样做\n"; }}},
{ L"0", {L"quit", [&running]{ running = false; }}},
}
};
std::locale::global(std::locale{"en_US.UTF-8"});
while (running) {
menu();
}
}
Jawaban
Jawaban atas pertanyaan Anda
nama parameter template - apakah bisa ditingkatkan?
Sebagian besar karena mereka tidak konsisten. Mulai ketik nama dengan huruf besar, dan pilih akhiran semuanya dengan Type
atau tidak. Saya menyarankan:
str
->Str
intype
->IStream
(hanya untuk memperjelas bahwa kami mengharapkan sesuatu seperti distd::istream
sini)outtype
->OStream
penggunaan default_in dan default_out - apakah lebih baik menyimpulkan default dari tipe string?
Ya, lihat di bawah.
pilihan
std::function<void()>
sebagai operasi untuk setiap pilihan
Di std::function<>
sini Anda perlu menyimpan fungsi untuk setiap pilihan di peta. Satu-satunya pertanyaan adalah apakah void()
tipe yang tepat untuk fungsi tersebut. Jika Anda ingin operator()()
mengambil parameter dan / atau mengembalikan nilai, maka Anda harus mengubah jenis fungsinya juga.
penggunaan std :: pair vs. objek khusus
Saya pribadi berpikir tidak masalah std::pair
.
haruskah saya membungkus semua ini dalam namespace?
Jika hanya class ConsoleMenu
, saya rasa tidak akan ada perbaikan untuk meletakkannya di namespace. Namun, saya akan meletakkan default_in
dan default_out
di namespace, karena nama-nama itu cukup umum, dan Anda tidak ingin mereka mencemari namespace global.
apakah ada fungsi yang hilang?
Saya tidak tahu, jika hanya ini yang Anda butuhkan maka itu lengkap. Jika Anda membutuhkan sesuatu yang lain darinya, itu tidak.
apakah ada cara untuk membuat versi constexpr?
Ya, dengan memastikannya memenuhi persyaratan LiteralType . Ini juga berarti bahwa semua variabel anggota harus merupakan LiteralTypes yang valid, dan mencegah penggunaan std::string
atau std::map
. Anda dapat menggunakan const char *
dan std::array
sebagai gantinya.
Meneruskan aliran input dan output berdasarkan nilai
Konstruksi yang Anda miliki di mana Anda meneruskan jenis aliran sebagai parameter templat, dan kemudian menyimpulkan aliran konkret darinya sangat aneh, tidak fleksibel, dan membutuhkan lebih banyak pengetikan daripada yang diperlukan. Cukup tambahkan aliran input dan output sebagai parameter ke konstruktor:
template <class str, class intype, class outtype>
class ConsoleMenu {
public:
ConsoleMenu(const str& message,
...,
intype &in,
outtype &out);
Membandingkan:
ConsoleMenu<std::wstring, std::wistream, std::wostream> menu{...}
Melawan:
ConsoleMenu<std::wstring> menu{..., std::wcin, std::wcout}
Jika Anda ingin input dan output standar menjadi parameter default, maka saya akan menyimpulkannya dari tipe string:
template <typename T> struct default_in;
template<> struct default_in<std::string> {
static std::istream& value() { return std::cin; }
};
template<> struct default_in<std::wstring> {
static std::wistream& value() { return std::wcin; }
};
...
template <class str, class intype, class outtype>
class ConsoleMenu {
public:
ConsoleMenu(const str& message,
...,
intype &in = default_in<str>::value(),
outtype &out = default_out<str>::value());
Karena dengan begitu Anda bisa menulis:
ConsoleMenu menu{L"Wide menu", L"invalid", L"> ", L". ", {/* choices */}};