C++ Kurs

friend Funktionen & Klassen

Die Themen:

Manchmal ist es notwendig, dass Klassen oder Funktionen Zugriff auf die geschützten Member einer anderen Klasse benötigen. Sehen wir uns dies wieder an einem Beispiel an.

Angenommen, wir haben eine Klasse Matrix, die eine 2-dimensionale Tabelle repräsentiert, und eine Klasse Vektor, die ein 1-dimensionales Feld enthält. Nun sollen alle Zeilen der Tabelle in der Klasse Matrix mit einem bestimmten Faktor multipliziert werden, wobei die Faktoren für die einzelnen Zeilen innerhalb der Klasse Vektor abgelegt sind. Da die Tabelle und der Vektor in der Regel in der private- oder protected-Sektion ihrer Klasse abgelegt sind, hat die Klasse Matrix keinen Zugriff auf die Faktoren in der Klasse Vektor und auch umgekehrt nicht. Wir könnten jetzt zwar die Eigenschaften als public deklarieren, würden damit aber den Zugriff auf die Eigenschaften generell freigeben, was wiederum auch nicht erwünscht ist. Der Ausweg aus diesem Dilemma heißt friend-Klasse, wenn wir einmal davon absehen, für die Klasse Vektor eine eigene Memberfunktion für die Rückgabe des Faktors zu implementieren.

PgmHeader// 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];
    ....
};

friend-Klassen

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 nachfolgenden Beispiel erhält also die Klasse Matrix vollen Zugriff auf alle Member der Klasse Vektor.

PgmHeader// 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 folgt aussehen.

PgmHeader// 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];
}

Dabei ist zu beachten, dass Vektor vor Matrix per Vorwärtsdeklaration deklariert 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 ebenfalls 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. Die Reihenfolge, wann was deklariert und definiert wird, ist nicht immer ganz einfach.

friend und Vererbung

Die friend-Eigenschaft einer Klasse wird niemals weitervererbt. Würde im vorherigen Beispiel von der Klasse Matrix, die 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 neue 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 spezifiziert werden.

friend-Funktionen

Außer den bisher behandelten friend-Klassen gibt es auch noch friend-Funktionen. friend-Funktionen gehören keiner Klasse an und haben ebenfalls 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.

PgmHeader// 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 der Klasse Base und ist von Base eine Klasse Any abgeleitet, so ist Func(...) nicht automatisch friend-Funktion von Any. Auch dies muss ebenfalls explizit angegeben werden.

Einsatz von friend-Funktionen

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. So wurde z.B. in einem vorangegangen Kapitel der Plus-Operator '+' der Klasse CString überladen, um folgende Anweisungen schreiben zu können:

stringObj3 = stringObj1 + stringObj2;
stringObj3 = stringObj1 + "any text";
PgmHeader// Klassendefinition
class CString
{
    ....
    CString& operator + (char* pT);
    CString& operator + (CString& op2);
};

D.h. zu einem CString Objekt konnte entweder ein weiteres CString Objekt oder ein ASCII-String hinzugefügt werden. 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 eine Funktion erhält die Funktion zwei Parameter. Im ersten Parameter wird der linke Operand übergeben und im zweiten Parameter der rechte. Für die obige Anweisung besitzt daher 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.

PgmHeader// 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;
}

Ein anderer Anwendungsfall für friend-Funktionen wurde im Verlaufe des Kurses schon mehrfach aufgeführt, nämlich beim Überladen der Operatoren '<<' und '>>' für die Ausgabe bzw. Eingabe von Objekten.

ÜbungEin Beispiel schenken wir uns an dieser Stelle und Sie können gleich zur Übung übergehen.