2009-08-19 16 views
7

Myślę, że problem jest dość powszechny. Masz ciąg wejściowy i musisz wywołać funkcję w zależności od zawartości napisu. Coś jak przełącznik() dla łańcuchów. Pomyśl o opcjach wiersza poleceń.Poszukuję najbardziej eleganckiego dyspozytora kodów

Obecnie używam:

using std::string; 

void Myclass::dispatch(string cmd, string args) { 
    if (cmd == "foo") 
     cmd_foo(args); 
    else if (cmd == "bar") 
     cmd_bar(args); 
    else if ... 
     ... 
    else 
     cmd_default(args); 
} 

void Myclass::cmd_foo(string args) { 
... 
} 

void Myclass::cmd_bar(string args) { 
... 
} 

aw nagłówku

class Myclass { 
    void cmd_bar(string args); 
    void cmd_foo(string args); 
} 

Więc każdy foo i bar muszę powtórzyć cztery (4!) Razy. Wiem, że mogę podawać wskaźniki funkcyjne i łańcuchy do statycznej tablicy przed i wykonuję dyspozycję w pętli, oszczędzając niektóre, jeśli ... inne linie. Ale czy istnieje jakieś makro oszustwo (lub nadużycie preprocesora, w zależności od POV), które pozwala w jakiś sposób zdefiniować funkcję i jednocześnie automatycznie aktualizować tablicę? Więc musiałbym napisać to tylko dwa razy, a może raz, jeśli jest używany inline?

Szukam rozwiązania w C lub C++.

+4

To pytanie było wielokrotnie zadawane, a odpowiedź była mi przeze mnie http://stackoverflow.com/questions/659581/replace-giant-switch-statement-with-what –

+2

On także prosi o jakiś schemat rejestracyjny do zmniejszyć wysiłek w utrzymaniu słownika. – djna

+0

Jest, ale nie sądzę, żeby był taki, który by go usatysfakcjonował. Możesz zrobić coś surowego i brzydkiego z makrami, ale to więcej kłopotów niż jest warte. –

Odpowiedz

1

brzydki makro rozwiązanie, które ze względu na rodzaj hasła. Zauważ, że nie rejestruje się automatycznie, ale zachowuje pewne rzeczy zsynchronizowane, a także spowoduje błędy kompilacji, jeśli dodasz tylko do mapowań, a nie funkcję w pliku źródłowym.

Mappings.h:

// Note: no fileguard 
// The first is the text string of the command, 
// the second is the function to be called, 
// the third is the description. 
UGLY_SUCKER("foo", cmd_foo, "Utilize foo."); 
UGLY_SUCKER("bar", cmd_bar, "Turn on bar."); 

Parser.h:

class Myclass { 
... 
protected: 
    // The command functions 
    #define UGLY_SUCKER(a, b, c) void b(args) 
    #include Mappings.h 
    #undef UGLY_SUCKER 
}; 

Parser.cpp:

void Myclass::dispatch(string cmd, string args) { 
    if (cmd == "") 
     // handle empty case 
#define UGLY_SUCKER(a, b, c) else if (cmd == a) b(args) 
#include Mappings.h 
#undef UGLY_SUCKER 
    else 
     cmd_default(args); 
} 

void Myclass::printOptions() { 
#define UGLY_SUCKER(a, b, c) std::cout << a << \t << c << std::endl 
#include Mappings.h 
#undef UGLY_SUCKER 
} 

void Myclass::cmd_foo(string args) { 
... 
} 
+0

Cóż, przyznane, jest brzydkie, ale wygląda na to, że zmniejszyło liczbę powtórzeń do dwóch. Ale #include w środku kodu jest naprawdę brzydkie. +1 – hirschhornsalz

+1

+1, z tego samego powodu. Ponadto, zakładając, że każde polecenie "foo" powoduje wywołanie cmd_foo, z odrobiną strufikacji można również uniknąć powtórzeń w parametrach. –

+0

Tak, myślę wzdłuż linii operatora "wklej" do. – hirschhornsalz

8

Brzmi jak szukasz w Command pattern

coś takiego:

Tworzenie mapy jak to

std::map<std::string, Command*> myMap; 

następnie wystarczy użyć przycisku, aby wykonać polecenie tak. ...

std::map<std::string, Command*>::iterator it = myMap.find(str); 
if(it != myMap.end()) { 
    it->second->execute() 
} 

Aby zarejestrować swoje polecenia, po prostu rób to

myMap["foo"] = new CommandFoo("someArgument"); 
myMap["bar"] = new CommandBar("anotherArgument"); 
+0

Nie. Prawdopodobnie używałem funkcji o nazwie "cmd_ *", ale może to być coś innego, nie trzeba reprezentować poleceń. Dodatkowo dyspozytor nie musi być związany w obiekcie. – hirschhornsalz

+2

@drhirsch. Nie muszą to być rzeczywiste polecenia. To tylko nazwa wzoru. Zasadniczo jest to sposób na wykonanie jakiegoś predefiniowanego kodu na podstawie danych wejściowych – Glen

+0

Myślę, że rozwiązanie, które sugerują Neil i Glen, jest właściwą odpowiedzią. Nie obejmuje to samodzielnej rejestracji, ale nie jestem pewien, czy jest to czysty sposób na zrobienie tego w C++. W Javie lub C# byłoby to jednak bardzo wykonalne. –

5

Podstawowym rozwiązaniem, za mój link w komentarzu zapytania, jest do odwzorowania ciągu znaków na jakieś wywołanie funkcji.

Aby właściwie zarejestrować ciąg -> funkcja pary wskaźnik/funktor:

Po pierwsze, mają singleton dyspozytor obiekt (szok horror!). Nazwijmy to TheDispatcherem - jest to wrapper dla map<string,Func>, gdzie Func to wskaźnik funkcji lub typ funktora.

Następnie mieć klasę rejestr:

struct Register { 
    Register(comst string & s, Func f) { 
     TheDispatcher.Add(s, f); 
    } 
}; 

Teraz w indywidualnych jednostkach kompilacji tworzenia statycznych obiektów (wstrząs grozy!):

Register r1_("hello", DoSayHello); 

Te obiekty będą tworzone (zakładając, kod nie znajduje się w bibliotece statycznej) i automatycznie zarejestruje się w TheDispatcher.

W czasie wykonywania wyszukuje się ciągi w TheDispatcher i wykonuje powiązaną funkcję/funktor.

1

Musisz przynajmniej zdefiniować funkcje i dodać je do rejestru. (Jeśli mają być nieinklarnymi elementami członkowskimi jakiejś klasy, będziesz musiał je również zadeklarować.) Poza językiem specyficznym dla domeny, generującym rzeczywisty kod (np. cjhuitt's macro hackery), nie widzę żadnej możliwości wspomnienia tych funkcji. (lub trzy) razy.

+0

Tak, myślę, że w końcu to dostałem w moją grubą czaszkę. – hirschhornsalz