In einigen Fällen kann es notwendig werden, dass andere Klassen oder Funktionen Zugriff auf die geschützten Member einer Klasse benötigen. Sehen wir uns dies wieder an einem Beispiel an.
Angenommen, Sie haben eine Klasse Matrix, die eine 2-dimensionale Matrix (Tabelle) repräsentiert, und eine Klasse Vektor, die ein 1-dimensionales Feld enthält. Nun wollen Sie die Daten der Matrix so verändern, dass alle Zeilen der Matrix Matrix mit einem bestimmten Faktor multipliziert werden. Diese Faktoren für die einzelnen Zeilen sind innerhalb der Klasse Vektor abgelegt. Da die Eigenschaften (Daten) in der Regel in der private- oder protected-Sektion einer Klasse abgelegt sind, können Sie von Matrix nicht auf die Faktoren in Vektor zugreifen und auch nicht umgekehrt. Sie könnten jetzt zwar die Eigenschaften als public deklarieren, würden damit aber den Zugriff auf die Eigenschaften generell freigeben, was wiederum in der Regel auch nicht erwünscht ist. Der Ausweg aus diesem Dilemma heißt friend-Klasse, wenn wir einmal davon absehen, der Klasse Vektor extra für die Matrizenoperation eine neue Memberfunktion zu spendieren.
// Jede Zeile des Feldes Matrix::aMatrix ist mit dem Faktor des
// Feldes Vektor::aVektor zu multiplizieren, also
// aMatrix[i][0..2] *= aVektor[i]
// Definition von Matrix
class Matrix
{
short aMatrix[3][3];
....
};
// Definition von Vector
class Vektor
{
short aVektor[3];
....
};
|
Damit eine Klasse Class1 Zugriff auf alle Member einer anderen Klasse Class2 erhält, wird die Klasse Class1 als friend-Klasse der Klasse Class2 deklariert. Dazu wird innerhalb der Klassendefinition der Klasse, die ihren 'Schutz' aufgibt, folgende Anweisung eingefügt:
| friend class FCLASS; |
FCLASS ist hierbei die Klasse, die uneingeschränkten Zugriff auf alle Member der Klasse erhalten soll. Im Beispiel erhält also die Klasse Matrix vollen Zugriff auf alle Member der Klasse Vektor.
// Definition von Matrix class Matrix { short aMatrix[3][3]; .... }; // Definition von Vektor // Erlaubt Matrix Zugriff auf alle Member class Vektor { friend class Matrix; short aVektor[3]; .... }; |
Da nun Matrix auf alle Member von Vektor zugreifen kann, könnte die notwendige Routine zur Multiplikation der Matrixdaten wie angegeben aussehen.
// Vorwärtsdeklaration von Vektor class Vektor; // Definition von Matrix class Matrix { short aMatrix[3][3]; void Multi(const Vektor& CV); .... }; // Definition von Vektor // Erlaubt Matrix Zugriff auf alle Member class Vektor { friend class Matrix; short aVektor[3]; .... }; // Definition von Matrix::Multi void Matrix::Multi(const Vektor& vekt) { for (int row=0; row<3; row++) for (int col=0; col<3; col++) aMatrix[row][col] *= vekt.aVektor[row]; } |
Beachten Sie bitte, dass Vektor vor Matrix definiert werden muss, da die Memberfunktion Multi(...) der Klasse Matrix als Parameter eine Referenz auf die Klasse Vektor erhält. Eine vollständige Definition der Klasse Vektor vor Matrix ist noch nicht möglich, da innerhalb von Vektor die Klasse Matrix als friend-Klasse deklariert wird. Außerdem kann die endgültige Definition der Memberfunktion Matrix::Multi(...) erst nach der Definition der Klasse Vektor erfolgen, da innerhalb der Memberfunktion auf die Eigenschaft aVektor von Vektor zugegriffen wird. Sie sehen, die Reihenfolge wann was deklariert und definiert wird ist nicht immer ganz einfach.
Die friend-Eigenschaft einer Klasse ist nicht vererbbar. Würde im vorherigen Beispiel von der Klasse Matrix, die ja friend-Klasse von Vektor ist, eine weitere Klasse SMatrix abgeleitet werden, so hätte SMatrix keinen Zugriff auf die geschützten Eigenschaften von Vektor. Das Gleiche gilt übrigens auch für den Fall, dass von Vektor eine weitere Klasse abgeleitet wird. Für diese neuen Klasse wäre die Klasse Matrix ebenfalls nicht automatisch friend-Klasse.
Außerdem gilt die friend-Eigenschaft nicht automatisch für die Umkehrfall, d.h. ist z.B. die Klasse Matrix friend-Klasse von Vektor, so ist Vektor nicht automatisch auch friend-Klasse von Matrix. Dieser Fall muss explizit deklariert werden.
Außer den bisher behandelten friend-Klassen gibt es auch noch friend-Funktionen. friend-Funktionen gehören keiner Klasse an und haben ebenfalls vollen Zugriff auf alle Member einer Klasse. Um eine Funktion als friend-Funktion einer Klasse zu deklarieren, wird wieder innerhalb der Klasse die ihren 'Schutz' aufgibt folgende Anweisung eingefügt:
| friend RTYP FNAME(...); |
RTYP ist der Returntyp der Funktion und FNAME der Funktionsname. Die Definition der Funktion erfolgt wie gewohnt, d.h. ohne Angabe der friend-Beziehung zu einer Klasse. Aus diesem Grund kann eine Funktion auch friend-Funktion von mehreren Klassen sein.
// Klassendefinition class Base { .... friend void DoAnything(); }; // Definition der friend-Funktion void DoAnything() { .... // Voller Zugriff auf Klasse Base } |
Bezüglich Ableitung von Klassen und friend-Funktionen gilt auch hier das Gleiche wie bei friend-Klassen. Ist eine Funktion Func(...) friend-Funktion von Klasse Base und ist von Base eine Klasse Any abgeleitet, so ist Func(...) nicht automatisch friend-Funktion von Any. Auch dies muss ebenfalls explizit deklariert werden.
Obwohl friend-Funktionen soweit wie möglich vermieden werden sollten, da sie die Datenkapselung aufbrechen, gibt es Fälle, wo es ohne friend-Funktion nicht geht. Erinnern Sie sich noch an die Klasse CString in einer der vorhergehenden Lektionen? Dort haben wir u.a. den Plus-Operator '+' überladen um folgende Anweisungen schreiben zu können:
stringObj3 = stringObj1 + stringObj2;
stringObj3 = stringObj1 + "any text";
// Klassendefinition
class CString
{
....
CString& operator + (char* pT);
CString& operator + (CString& op2);
};
|
D.h. wir konnten zu einem CString Objekt entweder ein weiteres CString Objekt oder einen ASCII-String hinzuaddieren. Was bisher nicht möglich war, ist folgende Operation:
stringObj3 = "any text" + stringObj2;
Der Grund hierfür ist, dass der überladene Operator '+' stets als Memberfunktion des linken Operanden aufgerufen wird. Und der linke Operand ist hier ein char*, was die Definition einer Memberfunktion unmöglich macht. Damit aber auch diese Operation durchgeführt werden kann, wird die nachfolgend angegebene friend-Funktion eingesetzt. Beim Überladen von binären Operatoren durch gewöhnliche Funktionen erhält die Funktion natürlich zwei Parameter. Der erste Parameter steht für den linken Operanden und der zweite Parameter für den rechten. Für die obige Anweisung besitzt der linke Operand den Datentyp const char-Zeiger und der rechte Operand ist eine Referenz auf ein CString Objekt. Und damit ergibt sich die unten dargestellte friend-Funktion.
// Klassendefinition class CString { .... CString operator + (const char *pT); CString operator + (const CString& op2); friend CString operator + (const char* pT, const CString& op2); }; // Definition der friend-Funktion CString operator + (const char* pT, const CString& op2) { CString temp(pT); temp = temp + op2; return temp; } |
Hinweis: Das Beispiel ist nur ein Auszug, selbstverständlich müssen Sie für die Klasse CString noch andere Operatoren wie z.B. den Zuweisungsoperator '=' überladen.
Eine weitere Ausnahme, wo es ohne friend-Funktion nicht geht, haben Sie ebenfalls in den vorherigen Lektionen kennen gelernt, nämlich beim Überladen der Operatoren '<<' und '>>' für die Ausgabe bzw. Eingabe von Objekten.
|
|
Ausgangsmatrix: |