Funktionsaufrufe
-
Hallo!
habe folgendes Problem:
baue einen Parser für ... etwa 200 Funktionen.
Wenn ich den Funktionsnamen fertig geparst habe, möchte ich aber nicht 199x
if(geparst == "Funktionsstring")
Funktionsaufruf();elseif.....
.
In C habe ich das elegant mit functionpointer gelöst. Also Buffer von Strukturen, die folgendermaßen aussahen:
struct{
char* name;
void*(ptFkt)(void);
}MyStruct;
MyStruct str[199]; //dem Sinn nachDann konnte einfach der Buffer nach den einzelnen strings durchgesucht werden und einfach über den Functionpointer die korrekte Funktion angesprungen werden.
Wie kann ich das am einfachsten in C++ erreichen?
Bitte um Ideen!
Danke T.O.
-
Vielleicht eine std::map?
#include <map> #include <string> typedef void(*Fn)(); typedef std::map<std::string, Fn> FnMap; //... FnMap myMap; //... void initMap() { myMap["func 1"] = func1; myMap["func 2"] = func2; // ...undsoweiter... } void callFunction(const std::string &name) { FnMap::iterator iter = myMap.find(name); if(iter != myMap.end()) (*iter->second)(); }Natürlich basiert das auf einer globalen Map mit globalen Funktionen. Natürlich(?) ist der Code nicht getestet. Aber es geht ja um's Prinzip.
Stefan.
-
Danke! Werd gleich mal map untersuchen!!

-
Der Ansatz ist super.
Mein Problem: Ich habe Kommandoklassen!
Also:Class A { string eins; string zwei; //usw funktion_eins(); funktion_zwei(); }; Class B { string drei; string vier; //usw funktion_drei(); funktion_vier(); };so auf die Art eben.
-
Da wird es etwas schwieriger (aber nur unwesentlich), weil du zu einem aufruf noch das Objekt benötigst. Du könntest eventuell alle Kommandoklassen von einer Basisklasse ableiten und dann die Kombination Objekt/Methode speichern:
class Command { public: typedef void Command::(*Command_Fun)(); } class A:public Command { public: void function1(); void function2(); ... }; class B:public Command { ... }; map<string,pair<Command*,Command::Command_Fun> > FnMap; FnMap["A1"]=make_pair(new A,A::function1); ... FnMap::iterator iter = myMap.find(name); if(iter != myMap.end()) (iter->second->first)->(*iter->second->second)();(genauso ungetestet wie DStefan's Code)
oder du verpasst jeder Kommandoklasse eine virtuelle Methode run(), die du aufrufen kannst:
map<string,Command> myMap; ... if((pos=myMap.find(name))!=myMap.end()) pos->second->run()
-
Hallo Theresa,
also wenn du dein Design schon auf Kommandoklassen aufbaust, würde ich es "sauberer" finden, wenn tatsächlich pro Kommando nur eine Klasse zuständig wäre. Natürlich funktionieren alle bisher geposteten Vorschläge, trotzdem möchte ich noch etwas vorstellen, das vielleicht noch besser ist. Nimm eine Klasse Dispatcher, bei der sich Unterklassen einer abstrakten Klasse Command anmelden können:
class Command { public: virtual ~Command() { } virtual std::string name() const { return _name; } virtual void run() = 0; protected: Command(const std::string &name) : _name(name) { } private: std::string _name; }; class Dispatcher { typedef std::map<std::string, Command *> MyMap; public: static Dispatcher &instance() { static Dispatcher inst; return inst; } void register(Command *cmd) { _myMap[cmd->name()] = cmd; } void runCommand(const std::string &name) { _myMap[name]->run(); } private: // Keine Konstruktion/Destruktion von außen: Dispatcher() { } ~Dispatcher() { } MyMap _myMap; };Wie du siehst, ist die Klasse ein Singleton. Der Einfachheit halber habe ich alle Funktionen inline gemacht, was zumindest im Falle von instance() vielleicht nicht so bleiben sollte. Ebenfalls der Einfachheit halber unterbleibt im Beispiel jede Fehlerbehandlung.
Mit dieser Lösung schlägst du gleich mehrere Fliegen mit einer Klappe:
- Es braucht im Programm keine zentrale Stelle mehr geben, an der die Kommandoliste gepflegt wird. Vielmehr kann man jedes Kommando für sich veranlassen, sich beim Dispatcher anzumelden.
- Der Dispatcher ist unabhängig von "realen" Kommandos, hängt also insbesondere nicht von den Unterklassen von Command ab.
- Um ein Kommando abzusetzen, braucht man keine Kenntnis von den realen Kommandos, sondern nur den Namen. Auch hier werden also Abhängigkeiten vermieden. Außerdem kannst du nun, falls notwendig, viele Stellen im Programm haben, von denen aus Kommandos abgesetzt werden. Man braucht ja nur Dispatcher::instance() aufzurufen, um das Singleton-Objekt zu erhalten.
Der Vollständigkeit halber, hier ein konkretes Kommando:
// In filecmd.h class CmdFileOpen: public Command { public: virtual void run(); protected: CmdFileOpen(const std::string &name); private: CmdFileOpen(); static CmdFileOpen _fileOpen; }; // In filecmd.cpp CmdFileOpen CmdFileOpen::_fileOpen; CmdFileOpen::CmdFileOpen() : Command("FileOpen") { Dispatcher::instance().register(this); } CmdFileOpen::CmdFileOpen(const std::string &name) : Command(name) { // Vielleicht nicht hier, sondern in der Unterklasse registrieren (s. Text)? Dispatcher::instance().register(this); } void CmdFileOpen::run(){ // Was auch immer.... }Hier registriert sich also CmdFileOpen im Konstruktor beim Start des Programms selbst. Dadurch, dass der Ctor private ist, kann man auch nicht mehrere Kommandos registrieren. Der protected Ctor sorgt dafür, dass eine Kommando-Hierarchie möglich ist. Vielleicht ist es übrigens nicht angebracht, dass dieser Ctor bereits eine Registrierung durchführt - immerhin ist die Unterklasse zu diesem Zeitpunkt nicht voll initialisiert.
Der Nachteil dieser Implementierung ist, dass man auf keinen Fall während der Initialisierung/Deinitialisierung des Programms (also vor oder nach main()) Kommandos absetzen oder sonstwie auf den Dispatcher zugreifen darf. Da die Reihenfolge, in der globale Objekte initialisiert und abgebaut werden, nicht definiert ist, kann das zu schwer reproduzierbaren Fehlern führen. Aus demselben Grund dürfen sich Command-Objekte auch nicht im Destruktor beim Dispatcher abmelden.
Ich finde dieses Entwurfsmuster sehr nützlich und habe es bereits mehrfach erfolgreich eingesetzt. Die oben genannten Nachteile habe ich eigentlich nie als störend empfunden.
Stefan.
-
Den Aufruf von Dispatcher::register könnte man auch noch in den Constructor der Basisklasse ablegen, dann brauchts nicht pro Ableitung geschrieben zu werden

-
Vielen Dank für die Vorschläge!
Ich habe jedoch ein kleines Verständnisproblemlä!map<string,pair<Command*,Command::Command_Fun> > FnMap; FnMap["A1"]=make_pair(new A,A::function1);Was genau passiert hier im pair<..,..>. Was passiert da mit Basisklasse und abgeleiteter? Als Command* und new A. Hier wird doch ein Objekt der Klasse A auf dem Heap mit Pointer Command* erzeugt. Irgendwie wird mir der Nutzen davon nicht ganz klar...
Gruss
Theresa
-
ein "Command*" kann auf ein beliebiges von Command abgeleitetes Objekt zeigen, also auch auf ein A - und da du für Aufrufe von A::xyz() ein A-Objekt benötigst, mußt du da ein passendes erzeugen. (wobei - so sicher bin ich mir auch nicht, daß das wirklich funktionieren kann)
-
Irgendwie kommt mir die Reihenfolge etwas komisch vor.
Basisklasse -> abgeleitete.
Mache Pointer von Basisklasse und wandle diesen in alle möglichen abgeleiteten.
Wäre nicht der umgekehrte Weg sinnvoll?
-
Ist doch auch der umgekehrte Weg: Erzeuge Pointer einer abgeleiteten Klasse und reiche ihn als Basisklassen-Pointer weiter.
-
LordJaxom schrieb:
Den Aufruf von Dispatcher::register könnte man auch noch in den Constructor der Basisklasse ablegen, dann brauchts nicht pro Ableitung geschrieben zu werden

Klar, könnte man. Aber wenn man so vorgeht, hat das Nachteile:
Erstens ist zum Zeitpunkt der Registrierung das Objekt der abgeleiteten Klasse nicht vollständig initialisiert. Das habe ich in meinem Beitrag schon (an anderer Stelle) erwähnt. Solange der Pointer beim Registrieren ohnehin nur gespeichert wird, ist das natürlich kein Problem.
Zweitens verhindert damit die Basisklasse, dass man Unterklassen erzeugt, die nicht gleich beim Erzeugen registriert werden. Im Beispiel wären das vielleicht Kommandos, die nur temporär vorhanden sind, also etwa je nach Status des Programms.Das ist also eine Design-Frage, denn beide Nachteile müssen in einem konkreten Programm ja nicht stören.
Stefan.
-
map<string,pair<Command*,Command::Command_Fun> > FnMap; FnMap["A1"]=make_pair(new A,A::function1);--> error C2440: 'Initialisierung': 'void (__thiscall A::* const )(void)' kann nicht in 'Command::Command_Fun ' konvertiert werden
Umwandlung von der Basis in die Ableitung erfordert 'dynamic_cast' oder 'static_cast'.
Wie soll der Functionpointer korrekt gesetzt sein, wenn Basispointer auf Funktion in abgeleiteter Klasse zeigt?