Schnittstellenzeiger (Zeiger auf Memberfunktionen)
Eigenschaftszeiger (Datenzeiger)
Während 'normale' Zeiger Adressen von Variablen oder Funktionen enthalten, enthalten Zeiger auf Klassenmember dagegen einen Offset auf das entsprechende Klassenmember innerhalb der Klasse. Der Grund für dieses abweichende Verhalten ist, dass von einer Klasse ja mehrere Instanzen (Objekte) bestehen können und damit eine absolute Adresse keine Sinn macht. Und dieses Verhalten hat Auswirkungen auf die Zugriffe über den Zeiger auf Klassenmember.
Beginnen werden wir mit Zeiger auf Memberfunktionen, den so genannten Schnittstellenzeigern. Wenn Sie wollen, können Sie zur Wiederholdung sich hier nochmals die Funktionszeiger vorher anschauen.
Nachfolgend ist die Ausgangsklasse für das Beispiel dargestellt. Die Klasse enthält u.a die Memberfunktion Init() zur Initialisierung der Eigenschaften und drei weitere Memberfunktionen StateX(), die später über einen Schnittstellenzeiger aufgerufen werden sollen.
|
class State { public: void Init(); void State1(); void State2(); void State3(); void Execute(); }; |
Definieren wir zunächst den Schnittstellenzeiger innerhalb der Klasse. Vielleicht erinnern Sie sich noch an die Definition des Funktionszeigers. Die Definition eines Funktionszeigers für eine Funktion die keinen Wert zurückgibt und auch keine Parameter benötigt sah wie folgt aus:
void (*pfnFunc) ();
wobei pfnFunc der Name des Funktionszeigers ist.
Schnittstellenzeiger werden fast auf die gleichen Art und Weise definiert, nur dass vor dem Zeigernamen noch der Klassenname angegeben wird, gefolgt vom Operator ::. Nachfolgend wurde der Klasse State der Schnittstellenzeiger pfnState hinzugefügt.
|
class State { void (State::*pfnState)(); // Definition des Schnittstellenzeigers public: void Init(); void State1(); void State2(); void State3(); void Execute(); }; |
Ist der Schnittstellenzeiger definiert, so kann ihm die 'Adresse' einer Memberfunktion wie folgt zugewiesen werden. Sehen wir uns zunächst wieder an wie Funktionszeigern die Adresse einer Funktion zugewiesen wird:
pfnFunc = Func;
Func ist der Name der Funktion, deren Adresse im Zeiger abgelegt werden soll.
Und auch hier sieht die Zuweisung einer 'Adresse' einer Memberfunktion zum Schnittstellenzeiger etwas anders aus. Im Gegensatz zu den Funktionszeigern, bei dem Sie nur den Funktionsnamen angeben müssen, müssen Sie hier den Adressoperator & und den Klassennamen gefolgt vom Operator :: vor dem Namen der Memberfunktion angeben. Damit sieht die Zuweisung an einen Schnittstellenzeiger innerhalb einer Memberfunktion wie angegeben aus.
|
void State::Init() { pfnState = &State::State1; } |
Bleibt nur noch der Aufruf der Memberfunktion übrig, deren Offset im Schnittstellenzeiger abgelegt ist. Im Beispiel wird in der Memberfunktion Execute() die im Zeiger pfnState referenzierte Memberfunktion aufgerufen.
|
void State::Execute() { (this->*pfnState)(); } |
Dieser Aufruf enthält zwei Neuigkeiten. Zum einen wird hier der Zeiger this verwendet, der später noch genauer beschrieben wird. Vorab nur so viel dazu: der this Zeiger enthält stets die Adresse des Objekts, in dessen Kontext er verwendet wird. Und zum anderen wird hier der neue Operator ->* verwendet. Beachten Sie bitte, dass ->* ein Operator ist und keine Kombination aus den beiden Operatoren -> und *. Der Operator ->* dient ausschließlich zum Aufruf von Memberfunktionen über Schnittstellenzeiger.
Wollen Sie Memberfunktionen nicht über einen innerhalb der Klasse definierten Schnittstellenzeiger aufrufen, sondern über einen außerhalb der Klasse definierten, so wird für die Definition des Zeigers die gleiche Syntax verwendet wie bei der Definition innerhalb der Klasse, d.h. auch hier muss vor dem Zeigernamen wieder der Klassenname und der Operator :: stehen.
|
// Klassendefinition class State { ... }; // Definition des Schnittstellenzeigers void (State::*pfnMember)(); |
Auch die Zuweisung einer 'Adresse' (eigentlich eines Offsets) zum
Zeiger ändert sich nicht gegenüber dem bisherigen. Lediglich der Aufruf
der Memberfunktion sieht etwas anders aus. Da im Schnittstellenzeiger
nur der Offset der Memberfunktion abgespeichert ist, muss jetzt vor dem
Zeigernamen das Objekt angegeben werden, dessen Memberfunktion
aufgerufen werden soll. Nach dem Objektnamen folgt der neue Operator
.*. Beachten Sie auch hier, das
.* ein Operator ist und nicht die
Kombination aus dem Operator . und dem Operator *.
|
// Klassendefinition class State { ... }; // Definition zweier Objekte State myState, yourState; // main() Funktion int main() { // Offset der Memberfunktion im Zeiger ablegen pfnMember = &State::State2; // Memberfunktion für myState Objekt aufrufen (myState.*pfnMember)(); // Memberfunktion für yourState Objekt aufrufen (yourState.*pfnMember)); ... }; |
Sehen wir uns noch an, wie über Zeiger auf die Eigenschaften einer Klasse zugegriffen wird. Die Definition des Eigenschaftszeigers erfolgt ebenfalls durch voranstellen des Klassennamens vor dem Zeigernamen. Auch die Zuweisung an einen Zeiger erfolgt in der gleichen Art wie beim Schnittstellenzeiger, d.h. zuerst kommt der Adressoperator, dann der Klassenname, dann der Operator :: und zum Schluss der Name der Eigenschaft.
Soll dann über diesen Zeiger eine Eigenschaft der Klasse verändert werden, so werden wieder die Operatoren .* bzw. ->* verwendet. Beachten Sie aber bitte, dass Sie über den Zeiger nur Zugriff auf die public Eigenschaften der Klasse haben.
|
// Klassendefinition class State { public: ... int data; }; // Definition des Memberzeigers int State::*pData; // main() Funktion int main() { // State Objekt und Zeiger definieren State myState, *pAnyState; // Objektzeiger initialisieren pAnyState = &myState; // Memberzeiger auf Datum (Offset!) pData = &State::data; // Datum im ersten Objekt setzen myState.*pData = 10; // Datum über Zeiger auslesen int var = pAnyState->*pData; ... } |
Ein Beispiel und eine Übung schenken wir uns an dieser Stelle.