C++ Kurs

Memberzeiger

Die Themen:

Schnittstellenzeiger (Zeiger auf Memberfunktionen)

Definition

Während 'normale' Zeiger Adressen von Variablen, Objekten oder Funktionen enthalten, enthalten Zeiger auf Klassenmember 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 keinen Sinn macht. Und dieses abweichende Verhalten hat Auswirkungen auf die Zugriffe über den Zeiger auf Klassenmember.

Beginnen werden wir mit Zeigern auf Memberfunktionen, den sogenannten Schnittstellenzeigern. Nachfolgend ist die Ausgangsklasse für unser 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 aus der Memberfunktion Execute() heraus aufgerufen werden sollen.

PgmHeader
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 eines Funktionszeigers. Die Definition eines Funktionszeigers auf eine Funktion die keinen Wert zurückgibt und auch keine Parameter benötigt sieht wie folgt aus:

void (*pfnFunc1) ();
// oder mittels decltype()
decltype(FNAME) *pfnFunc1;

wobei pfnFunc1 der Name des Funktionszeigers und FNAME der voll qualifizierte Name der Funktion 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, über den dann später die Memberfunktionen StateX() aufgerufen werden können.

AchtungBeachten Sie, dass hier vor dem Zeigername kein '*' steht. Außerdem sollten Sie die Zeigerdefinition nach der Deklaration der Memberfunktion stellen, damit Sie den decltype() Operator anwenden können.
PgmHeader
class State
{
 public:
   void Init();
   void State1();
   void State2();
   void State3();
   void Execute();
 private:
   decltype(&State::State1) pfnFunc;      // Definition des Schnittstellenzeigers
   // void (State::*pfnState)();          // Alternativ: Definition ohne decltype()

};

Ist der Schnittstellenzeiger definiert, so kann ihm die 'Adresse' einer Memberfunktion 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 nur den Funktionsname angegeben wird, muss hier der Adressoperator &, dann der Klassennamen, gefolgt vom Operator ::  und schließlich der Name der Memberfunktion angegeben werden. Damit sieht die Zuweisung an einen Schnittstellenzeiger innerhalb einer Memberfunktion wie folgt aus.

PgmHeader
void State::Init()
{
   pfnState = &State::State1;
}

Aufruf von Memberfunktionen über Schnittstellenzeiger

Bleibt 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.

PgmHeader
void State::Execute()
{
   (this->*pfnState)();
}

Dieser Aufruf enthält zwei Neuerungen. 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 immer die Adresse des Objekts, in dessen Kontext die Memberfunktion aufgerufen wurde. Und zum anderen wird hier der neue Operator ->* verwendet. Hier ist zu beachten, dass ->* ein Operator ist und keine Kombination aus den beiden Operatoren -> und *. Der Operator ->* dient ausschließlich zum Aufruf von Memberfunktionen über Schnittstellenzeiger.

Klassenlose Schnittstellenzeiger

Sollen Memberfunktionen nicht über einen innerhalb der Klasse definierten Schnittstellenzeiger aufgerufen werden, 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.

PgmHeader
// Klassendefinition
class State
{
   ...
};

// Definition des Schnittstellenzeigers
decltype(&State::State1) pfnMember;

Auch die Zuweisung einer 'Adresse' (eigentlich des Offsets) zum Zeiger ändert sich nicht gegenüber dem bisherigen, lediglich der Aufruf der Memberfunktion weicht ab. 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 .*. Es auch hier wieder zu beachten, dass .* ein Operator ist und nicht die Kombination aus den beiden Operatoren '.' und '*'.

PgmHeader
// Klassendefinition
class State
{
   ...
};
...
// Definition zweier Objekte
State myState, yourState;

// main() Funktion
int main()
{
   // Offset der Memberfunktion im Zeiger ablegen
   auto pfnMember = State::State2;
   // Memberfunktion für myState Objekt aufrufen
   (myState.*pfnMember)();
   // Memberfunktion für yourState Objekt aufrufen
   (yourState.*pfnMember)();
   ...
};

Eigenschaftszeiger (Datenzeiger)

Sehen wir uns noch an, wie über Zeiger auf die Eigenschaften einer Klasse zugegriffen werden kann. Die Definition des Eigenschaftszeigers erfolgt ebenfalls durch voranstellen des Klassennamens vor dem Zeigernamen. Auch die Zuweisung an einen solchen 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.

PgmHeader
// Klassendefinition
class State
{
 public:
   ...
   int data;
};

// Definition des Memberzeigers
decltype(&State::data) pData;   // alt: int State::*pData;

// main() Funktion
int main()
{
   // State Objekt definieren
   State myState;
   // Objektzeiger initialisieren
   auto pAnyState = &myState;
   // Memberzeiger auf Datum (Offset!)
   pData = &State::data;
   // Datum im ersten Objekt setzen
   myState.*pData = 10;
   // Datum über Zeiger auslesen
   auto var = pAnyState->*pData;
   ...
}

Ein Beispiel und eine Übung schenken wir uns an dieser Stelle.