weiter

Funktionsmap
Einführung

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);

Die Idee

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.

Der assoziative Container

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;

Zeiger auf Funktionen

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.