Yalnızca başlık genel konsol menüsü oluşturucu
Bir süre önce, bu cevabı komut satırı menüsü oluşturmayla ilgili bir soruya yazmıştım . Son zamanlarda ona atıfta bulundum ve geliştirmek istediğim birkaç şeyi fark ettim.
Amaç
Orijinal versiyonda olduğu gibi, amaç bir komut satırı (konsol) menüsünün yapımını ve kullanımını basitleştiren bir sınıfa sahip olmaktır.
Yaptığım iyileştirmeler:
std::string
veyastd::wstring
istemlere ve yanıtlara izin ver- kullanıcının seçicileri açıklamalardan ayırmasına izin verin
- her şeyi yalnızca başlık modülüne taşı
const
menülerin oluşturulmasına izin ver- seçicilere göre sırala
Sorular
Sorularım olan bazı şeyler şunlar:
- şablon parametre adları - iyileştirilebilirler mi?
- kullanımı
default_in
vedefault_out
- bu dize türünden anlaması varsayılan daha iyi olurdu? - seçimi
std::function<void()>
Her seçenek için işlem olarak std::pair
özel nesne kullanımına karşı- tüm bunları bir ad alanına sarmalı mıyım?
- herhangi bir işlev eksik mi?
- bir
constexpr
sürüm oluşturmanın bir yolu var mı?
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();
}
}
Bu, programın kullanımını ve menü işlevlerini oluşturmanın birkaç farklı yolunu gösterir. Konsolunuza ve derleyici ayarlarınıza bağlı olarak, Çince cümle düzgün görüntülenebilir veya görüntülenmeyebilir. Sırada geniş bir dizge versiyonu var.
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();
}
}
Yanıtlar
Sorularınıza cevaplar
şablon parametre adları - iyileştirilebilirler mi?
Çoğunlukla tutarsız olmalarıdır. Adlara büyük harfle başlayın ve hepsinin sonuna ekleyin Type
veya etmeyin. Öneririm:
str
->Str
intype
->IStream
(açık olmak gerekirse,std::istream
burada olduğu gibi bir şey bekliyoruz )outtype
->OStream
default_in ve default_out kullanımı - varsayılanları dize türünden çıkarmak daha iyi olur mu?
Evet, aşağıya bakın.
seçimi
std::function<void()>
Her seçenek için işlem olarak
std::function<>
Haritadaki her seçim için işlevleri kaydetmeniz gerekir . Tek soru, void()
fonksiyon için doğru tip olup olmadığıdır . Eğer isteseydik operator()()
parametreleri almak ve / veya değer döndürmek için, o zaman sen de fonksiyonun türünü değiştirmek gerekir.
std :: pair ve özel nesne kullanımı
Şahsen bunun iyi olduğunu düşünüyorum std::pair
.
tüm bunları bir ad alanına sarmalı mıyım?
Eğer adilse, class ConsoleMenu
onu bir isim alanına koymanın herhangi bir iyileştirme olacağını düşünmüyorum. Ancak, ben koyardı default_in
ve default_out
bir ad, o isimler oldukça jenerik olarak ve bunları genel ad alanını kirletmeye istemiyoruz.
herhangi bir işlev eksik mi?
Bilmiyorum, eğer tek ihtiyacın buysa, o zaman tamamlandı. Ondan başka bir şeye ihtiyacın varsa, o değil.
constexpr sürümü yapmanın bir yolu var mı?
Evet, LiteralType gereksinimlerini karşıladığından emin olarak . Bu aynı zamanda tüm üye değişkenlerinin geçerli LiteralTypes olması gerektiği ve bu, std::string
veya std::map
. Bunun yerine const char *
ve kullanabilirsiniz std::array
.
Giriş ve çıkış akışını değere göre geçirin
Şablon parametresi olarak bir akış türünü geçirdiğiniz ve daha sonra bundan somut bir akış çıkardığınız yapı çok tuhaf, esnek değildir ve gerekenden daha fazla yazım gerektirir. Yapıcıya yalnızca girdi ve çıktı akışını parametre olarak ekleyin:
template <class str, class intype, class outtype>
class ConsoleMenu {
public:
ConsoleMenu(const str& message,
...,
intype &in,
outtype &out);
Karşılaştırmak:
ConsoleMenu<std::wstring, std::wistream, std::wostream> menu{...}
E karşı:
ConsoleMenu<std::wstring> menu{..., std::wcin, std::wcout}
Standart giriş ve çıkışın varsayılan bir parametre olmasını istiyorsanız, bunu dize türünden çıkarırım:
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());
Çünkü o zaman yazabilirsin:
ConsoleMenu menu{L"Wide menu", L"invalid", L"> ", L". ", {/* choices */}};