Es gibt in C++ eine neue Form von Pointern, die nur auf Member in einer
speziellen Klasse gerichtet sein können. Sie sind dabei nicht mit
einer physikalischen Adresse verbunden -- sie verweisen also nicht auf
ein Datenfeld eines bestimmten Objekts der Klasse. Vielmehr beschreiben sie (bei
Pointern auf Datenmember) die Position des Members innerhalb der
Klasse. Sie werden intern durch die Adreßdifferenz des Datenmembers zum
Beginn des ganzen Objekts dargestellt. Ihr Einsatzgebiet ist dennoch das
gewöhnlicher Zeiger, also z.B. das Einsparen mehrfacher
Adreßberechnungen oder Fallunterscheidungen.
-
Betrachten wir als Beispiel einen Ausschnitt aus einer Listen-Klasse.
- class List
{
private:
struct lelem *first,*last;
int count;
// ...
};
Einen Pointer auf ein List-Klassenelement vom Typ int
bzw. auf ein
List-Klassenelement vom Typ struct lelem *
definiert man mit
- int List::* piL;
struct lelem * List::* ppslL;
So kann piL auf einen beliebigen List-Member vom Typ
struct lelem * zeigen, also beispielweise auf
first oder auf last. So sind die Pointer noch nicht
initialisiert, das könnte beispielsweise geschehen mit
- ppslL=&List::first;
piL=&List::count;
An dieser Stelle werden die Zugriffsrechte auf die angegebenen Member
überprüft.
Da in unserem Beispiel beide private sind, sind die beiden
Zeilen nur in einer Klassenfunktion erlaubt (oder bei friends,
etc.).
Da static-deklarierte Member nur einmal für die gesamte
Klasse und nicht
für jedes Objekt existieren, darf man auf sie keine solchen Pointer
konstruieren. In unserer Datumsklasse ist deshalb eine Definition
- int * Date::* pml = mlen;
nicht erlaubt, wohl aber die Konstruktion eines normalen Pointers
- int *pml=Date::mlen;
(Zugriffsrecht vorausgesetzt).
-
Das Pendant zu den Operatoren * und ->
für normale Pointer bilden
die neuen Operatoren .* und ->* für
Klassenelement-Pointer.
Sie sind zweistellig -- der linke Operand ist ein Objekt der Klasse
(.*) bzw. ein Pointer auf ein Objekt der Klasse
(->*),
der rechte ein Pointer auf ein Klassenelement dieser Klasse.
Innerhalb einer Klassenfunktion kann man auch * (ohne ersten
Operanden) benutzen -- auf das aktuelle Objekt bezogen.
Hier ein Beispiel beim Anhängen eines Werts an das Ende einer Liste: der
Zähler count für die Anzahl der Elemente muß erhöht
werden. Statt mit
++value; kann man das komplizierter wie folgt erledigen:
- void List::enqueue(int i)
{
int List::* piL=&List::value;
// ...
++value; // 1. Alternative!
++(this->*piL); // 2. Alternative!
++*piL; // 3. Alternative!
}
-
Obwohl es manchmal Sinn machen würde, ist Arithmetik mit solchen Pointern
nicht erlaubt (manche Compiler warnen allerdings nur und erzeugen dennoch
entsprechenden Code). Mit ++ könnte man zum Beispiel
mehrere aufeinanderfolgende Member desselben Typs nacheinander ansprechen. Da
das Layout der Klasse im Speicher aber nicht vorgeschrieben ist, sind solche
Operationen im ANSI-Entwurf auch nicht vorgesehen.
-
Intern werden die Pointer durch den Abstand des bezeichneten Elements
zum Beginn der Klasse +1 dargestellt, damit der Wert 0 für
das Pendant zum 0-Pointer verwendet werden kann.
-
Konversionen in "echte" (nicht klassengebundene) Pointer sind aus
offensichtlichen Gründen nicht erlaubt und auch nicht sinnvoll.
Das Erzeugen eines echten Pointers auf einen Member eines bestimmten
Objekts, der über einen Member-Pointer angesprochen wird, ist
natürlich möglich, etwa wie folgt:
- int List::*piL=&List::count;
List mylist;
int *p=&(mylist.*piL);
-
Analog zu normalen Pointern auf Funktionen kann man auch Member-Pointer auf
Elementfunktionen erzeugen. Intern muß hier natürlich anders
vorgegangen werden als bei den Offsets für Daten-Member.
Beispielsweise erzeugt man mit folgender Definition einen Member-Pointer
pfL auf die Elementfunktion enqueue von oben:
- void (List::*pfL)(int)=List::enqueue;
Ein Aufruf über diesen Pointer kann wie folgt stattfinden:
- (mylist.*pfL)(1); // entspricht mylist.enqueue(1);