Kann es eine Funktion ExecProcedure(string name) geben?



  • Ich brauche eine Funktion bool ExecProcedure(string name), die die Prozedur mit dem Namen name aufruft, true zurückgibt bei erfolgreichem Aufruf und false bei Nichtexistenz der Prozedur name. Zum Beispiel statt:

    string procedure;
    cout << "Welche Prozedur wollen Sie ausführen? ";
    cin >> procedure;
    switch(procedure)
    {
     case "a":
        a();
        break;
    
     case "b":
        b();
        break;
    
     case "xyz":
        xyz();
        break;
    
    // usw., bei vielen Prozeduren kann das richtig nervig werden, es sieht außerdem      // wenig elegant aus
    
     default:
        cout << "\nProzedur existiert nicht\n"
        break;
    }
    

    ginge dann einfach:

    string procedure;
    cout << "Welche Prozedur wollen Sie ausführen? ";
    cin >> procedure;
    if(!ExecProcedure(procedure))
     cout << "\nProzedur existiert nicht\n";
    

    Perfekt wäre es, wenn man auch Funktionen ausführen könnte, z. B. bool ExecFunction(string name, type* returnvalue), wobei hinterher in returnvalue der Rückgabewert enthalten ist.



  • Gefährliche Sache, die du da vorhast.
    An sich könntest du ein konstrukt à la
    (*name)(...)
    Verwenden, wobei du ... beispielsweise durch eine referenz vom gewünschten Typen ersetzt.
    Das ganze ist aber eine recht heikle Angelegenheit, von der ich abrate.



  • joa...ich bin mal so frei, dir eine halbe, aber funktionierende Lösung zu posten.
    Allerdings ohne Return-Wert.
    Bei dieser Lösung kommst du ohne jegliches if/else- oder switch-Konstrukt aus (das übrigens in deinem Code *falsch* ist).
    Wenn du return werte willst, sind diese eigentlich auch leicht zu ergänzen. sofern der typ immer gleich ist. Im anderen Fall könntest du eine virtuelle Basis-Klasse BaseFunctor bereitstellen oder so... ach, schau mal, ob du das nachvollziehen kannst:

    #include <iostream>
    #include <string>
    #include <map>
    
    using namespace std;
    
    // testfunktion 1
    void hallo()
    {
        cout << "hallo\n";
    }
    
    // testfunktion 2
    void answer()
    {
        cout << "42\n";
    }
    
    // *** für nen int-return Wert änderst du folgenden aus
    // *** typedef int (*iv_func)(); ab
    typedef void (*vv_func)();
    // globle map, die paare string -> funktion speichert
    map<string, vv_func> functions;
    
    // und gibts hier eine Referenz noch mit an, z.b.:
    // bool exec_procedure(const string& str, int& result)
    bool exec_procedure(const string& str)
    {
        map<string, vv_func>::iterator pos = functions.find(str);
        // prüfung, ob string vorhanden ist
        if(pos != functions.end()) {
            (pos->second)(); // aufruf der funktion
            // *** entsprechend waere der aufruf dann:
            // *** result = (pos->second)();
            return true;
        }
        // negativer return wert -> funktion nicht gefunden
        return false;
    }
    
    int main()
    {
        // map mit den beiden testfunktionen fuellen
        functions.insert(make_pair("hallo", &hallo));
        functions.insert(make_pair("answer", &answer));
    
        string input;
        cout << "Welche Prozedur wollen sie ausfuehren? ";
        cin >> input;
    
        // *** diese abfrage wuerde dann wie folgt aussehen mit return-Wert:
        // *** int result;
        // *** if(!exec_procedure(input, result)) 
        // kommentar s. exec_procedure -> false bedeutet, funktion wurde nicht gefunden
        if(!exec_procedure(input))
            cerr << "Prozedur nicht vorhanden\n";
    
        return 0;
    }
    

    Grüße, Xantus

    Edit:

    Das ganze ist aber eine recht heikle Angelegenheit, von der ich abrate.

    Bei 17 effektiven LOC ist das doch gar nicht so heikel.
    ~ Ich hab noch Kommentare zu evt. Code mit Return-Werten hinzugefügt (diese sind mit *** gekennzeichnet). Viel Spaß



  • Vielen Dank für den Code, ich werde es mal probieren, aber ich würde gerne verstehen was du da machst...

    Was heißt typedef?
    Wieso setzt du einen Funktionsnamen in Klamern und mit einem Stern davor:

    typedef void (*vv_func)();
    

    Wieso übergibst du den String als Referenz, er soll doch nicht verändert werden??

    Ich habe schon mal etwas von Maps gehört, aber ich weiß nicht, was du da mit der Map genau machst.

    Tut mir leid, aber ich verstehs echt nicht. Ich könnte mir jetzt auch alles einzeln im Internet zusammensuchen, aber dann hätte ich immer noch kein Gesamtbild. Eine Version, bei dem du die Kommentare zur ExecFunction() weglässt aber du dafür auskomentierst, was die einzelnen Befehle machen, wäre super.

    PS:

    Bei dieser Lösung kommst du ohne switch-Konstrukt aus (das übrigens in deinem Code *falsch* ist).

    Wieso ist mein switch-Konstrukt falsch? Ich kenne das so:

    switch(variable)
    {
    
     case wert1:
     // { hier können geschweifte Klammern stehen, müssen aber nicht
        function1();
        break;
     // }
    
     case wert2:
        function2();
        break();
    
     // die anderen Variablenwerte...
    
     default:
        defaultfunction();
        break; // muss hier nicht stehen, fals default am Ende steht, ich machs trotzdem immer
    } // end switch
    


  • dein switch konstrukt ist falsch, weil zeichenketten nicht für case nicht verwendet werden können, sondern nur integrale (ganzzahlige) (compile-time) konstanten.

    c++Beginner schrieb:

    Was heißt typedef?

    typedef führt alias-namen für bestimmte typen ein.

    typedef int foo;
    
    foo f = 42;
    //same as int f = 42
    

    so wird auch mit

    typedef void (*vv_func)();
    

    der alias n ame vv_func für den typen void(*)() eingeführt.
    void(*)() ist vom typ "funktionszeiger auf eine funktion, die nichts (void) zurückgibt und keine argumente übernimmt".

    Ich habe schon mal etwas von Maps gehört, aber ich weiß nicht, was du da mit der Map genau machst.

    eine map verbindet im prinzip zwei werte miteinander: einen schlüssel (key) und einen wert, den man mit diesem schlüssel (schnell) finden kann. besonders schnell und ähnlich wie ein switch/case wäre dabei eine hashmap. aber du musst hauptsächlich wissen, dass maps assoziative container sind, die einen (einmaligen) schlüssel mit einem wert in verbindung bringen.

    map<string, vv_func>
    

    in diesem fall ist der schlüssel ein string und der wert, auf den der schlüssel verweist ein zeiger auf eine funktion. wenn du als schlüssel nun funktionsnamen speicherst, bekommt jeder funktionsname einen zugehörigen funktionszeiger zugewiesen, über welchen du die passende funktion aufrufen kannst.

    ein minimalbeispiel, das auf funktionszeiger verzichtet, wäre in der art:

    #include <tr1/functional>
    #include <map>
    #include <string>
    #include <iostream>
    using namespace std;
    
    void hello ()
    {
       cout << "hello\n";
    }
    
    void answer ()
    {
       cout << "42\n";
    }
    
    int main ()
    {
       map<string, tr1::function<void()> function_map;
       //tr1::function "hält" sich einen funktionszeiger auf
       //eine void () - funktion.
    
       //nun wird die map gefüllt:
       function_map["hello"] = hello;
       function_map["answer"] = answer;
    
       //und so wird eine funktion aufgerufen:
       function_map["hello"]();
    }
    


  • Hi,
    da du ja selber lernen willst, gebe ich dir google-stichwörter, mit denen du dir die Syntax recht schnell erklären kannst:

    c++Beginner schrieb:

    Was heißt typedef?
    Wieso setzt du einen Funktionsnamen in Klamern und mit einem Stern davor:

    Google: Funktionspointer / Funktionszeiger

    Wieso übergibst du den String als Referenz, er soll doch nicht verändert werden??

    Ich übergebe ihn als konstante Referenz. Er soll nicht verändert werden, dafür ist das const da, er soll aber auch nicht kopiert werden (um Laufzeit zu sparen), dafür ist es eine Referenz.
    Google: const correctness

    Ich habe schon mal etwas von Maps gehört, aber ich weiß nicht, was du da mit der Map genau machst.

    Die map besteht aus den Key -> Value paaren
    string -> funktion
    mit
    std::map<T, U>::insert
    fügst du ein neues paar hinzu. Das erklärt dann auch, was make_pair macht 😉

    Bei dieser Lösung kommst du ohne switch-Konstrukt aus (das übrigens in deinem Code *falsch* ist).

    Wieso ist mein switch-Konstrukt falsch?

    Weil man nur Integrale Werte (int, long, enum) in einem Switch-Konstrukt vergleichen kann, du vergleichst aber String-Literale:

    case "xyz": // sollte einen Fehler erzeugen
        xyz()
        break;
    

    wo hängt es denn bei exec_procedure?
    Google hints: STL iterator, function pointer (siehe oben), std::map find (in der Referenz deiner Wahl, z.b. cppreference.com oder cplusplus.com)

    Wenn du kein (gutes) C++ (Einsteiger) Buch hast, ist es drigend an der Zeit, dir eins zuzulegen 😉

    - Edit: zu langsam -



  • case "xyz": // sollte einen Fehler erzeugen
        xyz() // sollte auch einen Fehler erzeugen ;) Flüchtigkeitsfehler
        break;
    

    Das heißt, immer wenn ich Strings mit festgelegten Werten vergleichen will, muss ich in if/else if/else-Konstrukt verwenden? Das ist aber blöd, gibt es da keine einfachere Lösung?

    Wenn du kein (gutes) C++ (Einsteiger) Buch hast, ist es drigend an der Zeit, dir eins zuzulegen 😉

    Ich hielt die durchgearbeiteten Bücher für gut 😉 Über Maps stand da auch was, was es ist, wusste ich auch noch, aber die einzelnen Funktionen dazu nicht.

    "Funktionszeiger" wurden gar nicht erwähnt, nur Pointer auf Objekte und Variablen.

    Danke für die Hilfe! Hätte auf keinen Fall gedacht, dass sowas geht.



  • Oh,

    ich habe gar nicht gesehen, dass in euren Funktionen die aufzurufenden Prozeduren bekannt sein müssen. Angenommen, sie sind nicht bekannt - fragt mich jetzt nicht wofür das gut ist - aber gibt es da auch eine Lösung?



  • Zu deiner switch-Frage: Nein, if ist da wohl erste Wahl.

    Zu deinem zweiten Post: Wie willst du etwas aufrufen, von dem du nicht weißt wie es heißt und wo es sich befindet? 😉

    Womöglich geht es mit plattformspezifischen Mittelchen (ich denke da an DLLs), aber mit Standardmitteln wohl nicht.



  • c++Beginner schrieb:

    Oh,

    ich habe gar nicht gesehen, dass in euren Funktionen die aufzurufenden Prozeduren bekannt sein müssen. Angenommen, sie sind nicht bekannt - fragt mich jetzt nicht wofür das gut ist - aber gibt es da auch eine Lösung?

    Was soll eine unbekannte Funktion Deiner Meinung nach denn tun ?

    Gruß,

    Simon2.



  • Sagen wir es so: Es ist möglich, aber wenn die Funktion bzw der SPeicher nicht existiert bzw nicht belegt ist, hast du ein Problem (:

    Das meinte ich mit der heiklen Angelegenheit.



  • Was soll eine unbekannte Funktion Deiner Meinung nach denn tun ?

    Keine unbbekante Funktion. Ich meinte, dass ich einmal eine Methode "ExecProcedure" definieren muss und mich dann aber nicht darum kümmern muss, jedes Mal einen neuen Eintrag in der Map zu erzeugen, wenn eine Funktion hinzukommt - dann ist die switch-Variante meistens sogar kürzer.



  • c++Beginner schrieb:

    Was soll eine unbekannte Funktion Deiner Meinung nach denn tun ?

    Keine unbbekante Funktion. Ich meinte, dass ich einmal eine Methode "ExecProcedure" definieren muss und mich dann aber nicht darum kümmern muss, jedes Mal einen neuen Eintrag in der Map zu erzeugen, wenn eine Funktion hinzukommt - dann ist die switch-Variante meistens sogar kürzer.

    Du musst die Funktionen die du verwendest auch bekannt machen. Sei es nun das du jedesmal den Code deiner ExecProcedure um einen neuen if/else-Fall erweiterst oder einen neuen Eintrag in eine map einfügst. Ohne geht es nicht (Eine Funktion hat keine Namen in dem Sinne, das dieser zur Laufzeit noch bekannt wäre).

    Irgendwie musst du also deinen Code anpassen.

    Das sauberste ist aber tatsächlich die Lösung mit der map.

    cu André



  • c++Beginner schrieb:

    Was soll eine unbekannte Funktion Deiner Meinung nach denn tun ?

    Keine unbbekante Funktion. Ich meinte, dass ich einmal eine Methode "ExecProcedure" definieren muss und mich dann aber nicht darum kümmern muss, jedes Mal einen neuen Eintrag in der Map zu erzeugen, wenn eine Funktion hinzukommt - dann ist die switch-Variante meistens sogar kürzer.

    nö, ist sie nicht, da du ja strings vergleichen musst, hast du nicht mal ne switch-Anweisung:

    bool ExecProcedure(const std::string& str)
    {
        if(str == "hallo") {  // zeile 1
            hallo();          // zeile 2
            return true;      // zeile 3
        }                     // zeile 4
        else if(str == "answer") {
            answer();
            return true;
        }
        else
            return false;   
    }
    

    das sind jeweils 4-5 Zeilen, wenn es ordentlich eingerückt und übersichtlich sein soll.

    Die Map-Variante benötigt nur 1 Zeile:

    functions.insert( make_pair("answer", &answer) );
    

    und gerade wenn die Funktionsanzahl 10+ erreicht, tut Beispiel 1 schon in den Augen weh. Und hinzufügen musst du die Funktionen so oder so!

    - Edit: zu langsam -



  • Auch ordentlich:

    bool ExecProcedure(const std::string& str)
    {
        if(str == "hallo")    // zeile 1
            hallo();          // zeile 2
        else if(str == "answer")
            answer();
        else
            return false;  
        return true;
    }
    


  • So, ich habe mal etwas ausprobiert, obwohl ich kein Fan von globalen Variablen bin. Ich übernehme für den Code auch keine Garantien:

    Ziel des Codes ist es, mit möglichst wenig gegenseitigen Wissen Funkionen über einen Namen aufzurufen:

    // main.cpp

    #include "exec_fn.h"
    
    int main()
    {
      exec::ExecuteFunction("ShowA");
      exec::ExecuteFunction("ShowB");
      exec::ExecuteFunction("ShowA");
      exec::ExecuteFunction("wait");
    }
    

    // exec_fn.h

    #if !defined(EXEC_FN_HEADER)
    #define EXEC_FN_HEADER
    
    #include <string>
    
    namespace exec
    {
      typedef void (*function_type)(); 
    
      void Register(const std::string& functionName, function_type function);
      void ExecuteFunction(const std::string& functionName);
      class RegisterFunction
      {
        public:
          RegisterFunction(const std::string& functionName, function_type function)
          {
            Register(functionName, function);
          }
      };
    }
    
    #endif
    

    // exec_fn.cpp

    #include "exec_fn.h"
    #include <map>
    
    namespace exec
    {
      std::map<std::string, function_type>& GetFunctionMap()
      {
        static std::map<std::string, function_type> functionMap;
        return functionMap;
      }
    
      void Register(const std::string& functionName, function_type function)
      {
        std::map<std::string, function_type>& functionMap = GetFunctionMap();
        if(functionMap.find(functionName) == functionMap.end())
          functionMap[functionName] = function;
      }
    
      void ExecuteFunction(const std::string& functionName)
      {
        std::map<std::string, function_type>& functionMap = GetFunctionMap();
        std::map<std::string, function_type>::iterator pos = functionMap.find(functionName);
        if(pos != functionMap.end())
          (pos->second)();
      }
    }
    

    Zudem noch 3 weitere Dateien die aber alle den gleichen Aufbau haben (Eine void-Funktion ohne Parameter, eine globale Variable...). Ich zeige hier nur die Implementierung von der wait.h/wait.cpp.

    // wait.h

    #if !defined(WAIT_HEADER)
    #define WAIT_HEADER
    void wait();
    #endif
    

    // wait.cpp

    #include "exec_fn.h"
    #include <iostream>
    
    void wait()
    {
      std::cin.clear();
      std::cin.ignore(std::cin.rdbuf()->in_avail());
      std::cin.get();
    }
    
    // Hiermit wird die wait-Funktion registriert
    exec::RegisterFunction wait_("wait", &wait);
    

    Die restlichen Header/Sourcen kann man sich wohl selbst ausdenken...

    Ob man dies als Sauber bezeichnen kann wage ich aber zu bezweifeln (Getestet unter VC++ 2005).

    cu André



  • c++Beginner schrieb:

    Was soll eine unbekannte Funktion Deiner Meinung nach denn tun ?

    Keine unbbekante Funktion. Ich meinte, dass ich einmal eine Methode "ExecProcedure" definieren muss und mich dann aber nicht darum kümmern muss, jedes Mal einen neuen Eintrag in der Map zu erzeugen, wenn eine Funktion hinzukommt ..

    Ach so: Du möchtest also eine "automatische Externalisierung", sobald Du eine Funktion hinzufügst.

    Ich weiß gar nicht, ob mir das erstrebenswert erschiene .... aber da der Compiler das nicht kann, geht's halt auch nicht.

    Gruß,

    Simon2.


Anmelden zum Antworten