Funktions-Pointer (*)()In C und C++ können Funktionen nicht nur durch die Angabe eines Symbols aufgerufen werden, sondern auch mittels eines Funktionspointers. Ein solcher Funktionspointer speichert die Adresse der aufzurufenden Funktion und hat als Typ einen Pointer auf eine Funktion bestehend aus Rückgabetyp und Parameterliste.
DetailsMittels Funktionspointer ist es möglich, einer Variablen den Pointer auf eine Funktion zuzuordnen, welche an anderer Stelle im Programm durch einen Aufruf ebendieser Variablen angesprochen werden kann. Einem Funktionspointer kann ein beliebiger Pointer auf eine Funktion zugewiesen werden, solange die Funktion denselben Funktionstyp hat, wie dass der Funktionspointer erwartet. An der Stelle des Aufrufes ist grundsätzlich nicht bekannt, welche Funktion in der Variablen gespeichert ist. Dennoch erfolgt der Aufruf der Funktion wie ein normaler Funktionsaufruf: Es werden Argumente übergeben und die Funktion liefert einen Rückgabewert. Aus dieser Überlegung heraus wird ein Funktionspointer in gewissen Situationen auch als Die Ausprogrammierung von Funktionspointer ist etwas verwirrend, wird hier jedoch Schritt für Schritt erklärt. Weiter unten findet sich zudem eine Zusammenfassung von häufig gebrauchten Deklarationen im Zusammenhang mit Funktionspointer. Ganz am Ende werden einige Anwendungen angegeben, bei denen Funktionspointer nützlich sein können. Funktionspointer in CDie Deklaration eines Funktionspointers muss folgendermassen geschrieben werden:
Der Rückgabetyp sowie die Parameterliste entsprechen dem Prototypen der Funktion, welche in der Variablen gespeichert werden soll. Die Parameterliste kann mit oder ohne Parameternamen angegeben werden, muss jedoch stets die Typen der Parameter beinhalten. Der Name der Variablen, welche mit dem Funktionspointer deklariert wird, steht mit einem vorne angehängten Pointer-Zeichen * in runden Klammern. Die zusätzlichen Klammern sind zu verstehen als eine Klammerung des Typ-Ausdrucks. Diese Klammern sind notwendig, um dem Compiler mitzuteilen, dass es sich hier um einen Pointer auf eine Funktion handelt und nicht um eine Funktion, welche einen Pointer zurückgibt. Würde man die Klammern weglassen oder das Pointer-Zeichen ausserhalb der Klammern schreiben, so würde diese Zeile vom Compiler als eine normale Funktionsdeklaration angesehen. Die Zuweisung zu einem Funktionspointer kann auf zwei Arten erfolgen:
Die erste Zeile zeigt die ursprünglich korrekte Variante, bei der dem Symbol, welches den Funktionspointer representiert, mittels des Adress-Operators die Adresse der Funktion zugewiesen wird. Die zweite Variante ist eine vereinfachte Schreibweise (sogenannter Syntactic Sugar) und bewirkt genau dasselbe. Diese Kurzform ist in den heutigen Compilern normalerweise zulässig. Auf dieser Seite wird zum besseren Verständnis stets die erste Variante benutzt, da sie explizit auf das Ansprechen der Adresse hinweist. Der Aufruf der Funktion kann ebenfalls auf zwei Arten erfolgen:
Wiederum bezeichnet die erste Zeile die ursprünglich korrekte Variante, wobei die zweite Zeile eine vereinfachte Schreibweise aufzeigt. Die zusätzlichen runden Klammern in der ersten Zeile sind aufgrund der Operatorenrangfolge notwendig: Der Funktionsaufruf-Operator hat höhere Priorität als der Dereferenz-Operator. Funktionspointer in C++In C++ können Funktionen einem Namespace zugeordnet sein, welcher durch die Keywords Befindet sich die gewünschte Funktion jedoch innerhalb eines Namespaces, so sind einige weitere Angaben notwendig. Hier wird nun zuerst die Zuweisung und danach die Deklaration und der Aufruf eines Funktionspointers aufgeführt. Die Zuweisung eines Funktionspointers, welcher auf eine Funktion innerhalb eines Namespace zeigt, muss folgendermassen geschrieben werden:
Bei der Deklaration sowie dem Aufruf des Funktionspointers, welcher auf eine Funktion innerhalb eines Namespaces zeigt, muss zusätzlich unterschieden werden zwischen statischen und nicht-statischen Funktionen. Funktionspointer auf statische Funktionen können genauso wie Funktionspointer auf globale Funktionen deklariert und aufgerufen werden. Bei nicht-statischen Funktionen handelt es sich um Objekt-Methoden von Klassen und Structs. Nicht-statische Funktionen erwarten stets implizit einen Um einen Funktionspointer auf eine nicht-statische Funktion zu deklarieren, muss der Namespace mittels des Bereich-Operators :: vor dem Pointer-Zeichen angegeben werden:
Damit wird dem Compiler mitgeteilt, dass der Funktionspointer auf eine nicht-statische Funktion innerhalb der angegebenen Klasse zeigt. Der Funktionstyp ist somit eine Funktion, welche einen impliziten this-Pointer der angegebenen Klasse erwartet. Anmerkung: Der Autor konnte bislang noch keine vollauf befriedigende Erklärung für die Schreibweise Durch das implizite this-Objekt ist ein Aufruf des Funktionspointers nicht mehr durch das einfache Ansprechen des Pointers möglich. Die nicht-statische Funktion muss mit einem Pointer-Member-Operator ->* oder Feld-Member-Operator .* aufgerufen werden. Diese beiden Operatoren erzwingen die Angabe eines Objekts auf dem die Funktion operieren kann.
Man beachte, dass sowohl bei der Deklaration als auch bei dem Aufruf eines Funktionspointers auf eine nicht-statische Funktion innerhalb einer Klasse keine vereinfachte Variante wie bei globalen Funktionen existiert. Um das Verständnis von Funktionspointer auf nicht-statische Funktionen zu vertiefen, wird hier noch erklärt, wie man ausserhalb einer Klasse auf einen Funktionspointer auf eine nicht-statische Funktion zugreifen kann, wenn der Funktionspointer selbst Teil derselben Klasse ist. Der Aufruf dieses Funktionspointers ausserhalb der Klasse muss folgendermassen geschrieben werden:
Diese Programmzeile wird folgendermassen vom Compiler interpretiert: Zuerst wird die Variable des angegebenen Objektes dereferenziert (innerste Klammer), was einen Funktionspointer auf eine nicht-statische Funktion der Klasse des Objektes zurückgibt. Um diesen Funktionspointer aufzurufen, muss wiederum dasselbe Objekt als das Objekt angegeben werden, auf welchem die Funktion operieren kann. Funktionspointer als RückgabewertFunktionspointer können auch als Rückgabewert einer Funktion auftreten. Um eine Funktion mit dem entsprechenden Funktionspointer-Typ als Rückgabetyp zu deklarieren, muss der Name der Funktion zusammen mit ihrer Argumentenliste in die runden Klammern () mit dem Pointer-Zeichen * geschrieben werden. In folgendem Beispiel wird eine Funktion
Wenn der Rückgabetyp auf nicht-statische Funktionen innerhalb eines Namespace zeigt, muss wie oben beschrieben zusätzlich der Namespace mit dem Bereichs-Operator :: vor dem Pointer-Zeichen angegeben werden.
Verwendung von typedefsDie Deklaration von Funktionspointern ist verwirrend und kann leicht zu Fehlinterpretationen beim Betrachten des Codes führen. Da es sich jedoch bei Funktionspointer um Typen handelt, ist es genauso wie bei jedem anderen Typ auch möglich, mittels Im folgenden Beispiel wird dem Symbol
Das Beispiel des vorherigen Abschnittes kann dadurch folgendermassen geschrieben werden:
Durch die Verwendung von typedefs kann der Funktionspointer-Typ genauso wie jeder andere Typ verwendet werden. ZusammenfassungHier sind nochmals einige Fälle aufgelistet in einem einzigen Programm. Man beachte dabei, dass zur Vereinfachung sämtliche Member der Klasse als
AnwendungenFunktionspointer sind heutzutags nicht mehr häufig in Gebrauch. In C++ wird mit virtuellen Methoden eine andere Möglichkeit geboten, Funktionen ohne explizite Kenntnis aufzurufen. Dennoch werden hier einige Anwendungsbeispiele für Funktionspointer aufgeführt: Es ist üblich, Datenstrukturen so zu programmieren, dass die eigentlichen Inhalte nicht direkt angesprochen werden können, sondern nur über Hilfs-Funktionen. Dadurch wird der eigentliche Inhalt abstrahiert und es ist nicht notwendig, die Art der Ausprogrammierung zu kennen. Einige Hilfs-Funktionen erwarten dementsprechend einen Handler, welcher die gewünschten Inhalte verändert, wo auch immer sie sich befinden. Iteratoren beispielsweise durchlaufen gleichwohl Listen als auch Arrays, Bäume und weitere Strukturen. Häufig können solchen Iteratoren ein Handler mitgeliefert werden, welcher auf das aktuelle Element des Iterators angewendet wird. Funktionspointer werden hierbei häufig für Konvertierungen verwendet und zeigen oftmals auf relativ kleine Funktionen. Durch solche Konstrukte ist es schlussendlich auch möglich, eine Funktion auf eine gesamte Datenstruktur anzuwenden. Solche Konstrukte werden in höheren Sprachen oftmals mit eingebauten Keywords wie Eine weitere Anwendung von Funktionspointer findet sich in der Geschwindigkeitsoptimierung. Wenn innerhalb einer Schleife aufgrund einer Bedingung ständig mittels Nach wie vor weit verbreitet ist die Verwendung von Funktionspointer für State-Machines. State-Machines sind Programme, welche einen oder mehrere Einstellungs-Parameter besitzen, die sich nur durch eine explizite Ansprechung durch den Programmierer ändern. Für jede mögliche Einstellung führt das Programm unterschiedliche Anweisungen aus. Um die verschiedenen Ausführungen zu koordinieren, werden je nach Einstellung andere interne Funktionen aufgerufen, welche normalerweise als Funktionspointer angesprochen werden können. Die Änderung einer Einstellung bewirkt somit nur die Änderung eines Funktionspointers, ansonsten bleibt die Maschine unangetastet. Ein Beispiel für eine State-Machine ist OpenGL. Ebenfalls aus dem Bereich der State-Machines kommt die Überlegung der Callback-Function (Rückruf-Funktion). Eine State-Machine erlaubt es dem Programmierer beispielsweise, eine bestimmte Funktion aufzurufen, wenn ein gewisses Ereignis eintritt. Durch die Angabe eines Funktionspointers kann der Programmierer der Maschine mitteilen, welche Funktion in diesem Falle aufgerufen werden soll.
|