Yalnızca başlık genel konsol menüsü oluşturucu

Dec 20 2020

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:

  1. std::stringveya std::wstringistemlere ve yanıtlara izin ver
  2. kullanıcının seçicileri açıklamalardan ayırmasına izin verin
  3. her şeyi yalnızca başlık modülüne taşı
  4. constmenülerin oluşturulmasına izin ver
  5. seçicilere göre sırala

Sorular

Sorularım olan bazı şeyler şunlar:

  1. şablon parametre adları - iyileştirilebilirler mi?
  2. kullanımı default_inve default_out- bu dize türünden anlaması varsayılan daha iyi olurdu?
  3. seçimi std::function<void()>Her seçenek için işlem olarak
  4. std::pairözel nesne kullanımına karşı
  5. tüm bunları bir ad alanına sarmalı mıyım?
  6. herhangi bir işlev eksik mi?
  7. bir constexprsü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 &in;
    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

3 G.Sliepen Dec 20 2020 at 19:16

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 Typeveya etmeyin. Öneririm:

  • str -> Str
  • intype-> IStream(açık olmak gerekirse, std::istreamburada 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 ConsoleMenuonu bir isim alanına koymanın herhangi bir iyileştirme olacağını düşünmüyorum. Ancak, ben koyardı default_inve default_outbir 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::stringveya 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 */}};