![]() |
Funktionsmap
|
G.Eichelsdörfer - Staatliche Technikerschule Weilburg |
Die folgende Idee wurde von Herrn Tim Otterbach am 2005-04-14 angeregt. Herr Otterbach suchte nach einer Möglichkeit, ein umfassendes CGI-Programm in C++ zu erstellen, in welchem spezielle Datenbankanwendungen in entsprechenden Funktionen implementiert sind. Im gesendeten Query-String eines HTML-Formulars soll das CGI-Programm einen Wert erhalten, der die aufzurufende Funktion festlegt.
Dies kann relativ starr mit Hilfe von kaskadierten zweiseitig bedingten Anweisungen umgesetzt werden.
fname
möge den Namen der aufzurufenden Funktion beinhalten.
Im folgenden Prinzipbeispiel sind f1
, f2
und f3
die implementierten Funktionen.
if(fname=="f1") f1(Parameter);
else if(fname="f2") f2(Parameter);
else if(fname="f3") f3(Parameter);
else cout << "<p>Funktion existiert nicht.</p>";
Statt dieser etwas unflexiblen Codierung ist eine andere Implementation interessant,
in welcher ein assoziativer Funktionsaufruf verwendet wird. So kommt man ohne die
kaskadierten bedingten Anweisungen aus. Der Funktionsaufruf kann etwa wie folgt
codiert werden:
function[fname](Parameter);
Für einen assoziativen Funktionsaufruf ist ein assoziativer Container für Schlüssel-Wert-Paare geeignet. Der Schlüssel ist der zur Laufzeit erhaltene String mit dem Funktionsnamen, der dem Schlüssel zugeordnete Wert ist die entsprechende Funktion bzw. die Funktionsreferenz. Diese Referenz ist die Anfangsadresse der Funktion im Arbeitsspeicher, die in einem Funktionszeiger abgelegt werden kann.
Als assoziativer Container kann eine map
verwendet werden,
die in der Standardbibliothek jedes aktuellen C++-Compilers enthalten ist.
Eine std::map
ist ein Klassentemplate. Zur Erzeugung einer Klasse
auf Basis des Templates müssen zwei Template-Typparameter angegeben werden.
Der erste Typparameter legt den Typ des Schlüssels fest, der zweite den Typ des Wertes.
Als Schlüsseltyp ist hier std::string
gut geeignet.
Als Werttyp wird ein Zeigertyp auf Funktionen benötigt.
Definition der benötigten Klasse:
typedef std::map <std::string,?Zeiger auf Funktionen?> funcmap;
Um einen Zeiger auf Funktionen als Typ zu definieren muss zunächst ein Funktionstyp festgelegt bzw. beschrieben werden. Hier wird sich ein kleiner Nachteil dieses Verfahrens zeigen. Mit der Festlegung eines Funktionstyps ist die Festlegung der Signatur, also der Parametertypen, verbunden. Es können somit nur Funktionen assoziativ aufgerufen werden, die diese Signatur besitzen. Dies ist aber in vielen Fällen kein wesentlicher Nachteil. Man kann auch optionale Paremeter vorsehen.
Beispielhafte Definition des Funktionstyps:
typedef bool specfunc(std::string);
Diese Definition ist von der Programmiererin nach eigenem Bedarf vorzunehmen.
Ein Zeiger auf eine solche Funktion wird, wie bei Datentypen auch, durch ein Asterisk
hinter dem Typnamen beschrieben: specfunc*
Damit kann die map-Klasse fertiggestellt werden:
typedef std::map <std::string,specfunc*> funcmap;
Bevor man die Früchte dieser Vorbereitung ernten kann, müssen die Schlüssel-Wert-Paare
aus Laufzeit-Funktionsnamen und Funktionszeiger in den Container vom Typ
funcmap
gelegt (eingefügt) werden.
Um ein solches Paar zu erzeugen, braucht man eine Funktion und den ihr zugeordneten
Laufzeit-Namen. Um aus beiden eine Paar zu generieren, kann der sogenannte
value_type
der map verwendet werden.
Beispielauszug:
bool myfunc(std::string);
...
funcmap::value_type("MyFunc",*myfunc);
"MyFunc" ist der Laufzeitname der Funktion myfunc.
*myfunc ist die Anfangsadresse der Funktion myfunc.
Dieses Paar ist in die Funktionsmap einzufügen.
Dafür steht die map-Methode insert
zur Verfügung:
funcmap function; // Die Funktionsmap function wird angelegt
// und darin das Paar ("myfunc",*myfunc) eingefügt.
function.insert(funcmap::value_type("MyFunc",*myfunc));
Nach dieser Vorlage sind die anderen benötigten Funktionen in die map einzufügen.
Danach gelingt z.b. der folgende Aufruf:
function["MyFunc"]("Hallo");
Diese Anweisung lässt in der map function
nach dem Schlüssel
"MyFunc" suchen und liefert die diesem Schlüssel zugeordnete Anfangsadresse
einer Funktion - hier die Adresse von myfunc. Der entscheidende Vorteil liegt jedoch
in der Verwendung einer Variablen:
std::string FuncName;
cin >> FuncName;
function[FuncName]("Hallo");
So kann zur Programmlaufzeit festgelegt werden, welche der eingetragenen Funktionen aufgerufen werden soll.
Es folgt ein vollständiger Quellcode zur Demonstation dieses Verfahrens. Dort wird zusätzlich ein Iterator verwendet um sowohl die Robustheit als auch die Performance des Programms zu steigern.