friend-Funktionen und Klassen
Manchmal ist es notwendig, dass Klassen oder Funktionen Zugriff auf die geschützten Member einer anderen Klasse haben. Einen dieser Fällen haben wir beim Überladen der Operatoren << und >> für die Ein-/Ausgabe kennengelernt.
Sehen wir uns einen weiteren Fall an. Angenommen, wir haben eine Klasse Matrix, die eine Tabelle enthält, und eine Klasse Vektor mit einem 1-dimensionalen Feld.
#include <print>
#include <iostream>
#include <cstdlib>
constexpr int SIZE = 3;
// Definition der Klasse Matrix
class Matrix
{
short data[SIZE][SIZE]; // Datenfeld
public:
// ctor, belegt Feld mit Zufallszahlen
Matrix()
{...};
// dtor
~Matrix() = default;
// Ausgabe Daten
friend std::ostream& operator << (std::ostream& out, const Matrix& obj);
};
std::ostream& operator << (std::ostream& out, const Matrix& obj)
{...}
// Definition der Klasse Vector
class Vector
{
short data[SIZE];
public:
// ctor, belegt Feld mit Zufallszahlen
Vector()
{...}
// dtor
virtual ~Vector() = default;
// Ausgabe der Daten
friend std::ostream& operator << (std::ostream& out, const Vector& obj);
};
std::ostream& operator << (std::ostream& out, const Vector& obj)
{...}
Nun sollen alle Werte der Zeile n in der Tabelle Matrix mit dem Faktor Vektor[n] multipliziert werden.
Matrix[n][...] *= Vektor[n];
Die Deklaration der dazu notwendinge Methode von Matrix könnte wie folgt aussehen:
class Matrix
{
...
Matrix& operator *= (this Matrix& self, const Vector& vect);
}
Da die Klasse Vector aber erst nach der Klasse Matrix definiert ist, wird der Compiler sich beschweren, dass er Vector nicht kennt. Dementsprechend ist die Klasse Vector vor der Klasse Matrix zu definieren.
Jedoch löst das nur das halbe Problem, denn innerhalb des überladenen Operators *= von Matrix muss noch auf die privaten Daten von Vector zugegriffen werden. Eine Lösung ist, in Vector eine entsprechende Methode zu definieren, die das gewünschte Datum zurückliefert. Eine andere Lösung wäre die Verwendung einer friend-Klasse.
Versuchen Sie in Ihren Anwendungen, so weit wie möglich, ohne friend auszukommen, da dies die Datenkapselung aufbricht. Die nachfolgenden Ausführungen dienen nur zur Demonstration, wie friend-Funktionen und -Klassen angewandt werden.
friend-Klassen
Damit eine Klasse A Zugriff auf alle Member einer Klasse B erhält, wird die Klasse A als friend-Klasse der Klasse B deklariert. Hierzu wird innerhalb der Klassendefinition der Klasse, die ihren 'Schutz' aufgibt, folgende Anweisung eingefügt:
friend class FCLASS;
FCLASS ist die Klasse, die uneingeschränkten Zugriff auf alle Member der Klasse erhalten soll. Im nachfolgenden Beispiel erhält die Klasse Matrix Zugriff auf alle Member der Klasse Vector.
#include <print>
#include <iostream>
#include <cstdlib>
constexpr int SIZE = 3;
// Definition der Klasse Vector
class Vector
{
short data[SIZE];
public:
// ctor, belegt Feld mit Zufallszahlen
Vector()
{
for (size_t index=0; index<SIZE; index++)
data[index] = index+1;
}
// dtor
virtual ~Vector() = default;
// Ausgabe der Daten
friend std::ostream& operator << (std::ostream& out, const Vector& obj);
friend class Matrix;
};
std::ostream& operator << (std::ostream& out, const Vector& obj)
{
for (auto elem: obj.data)
std::print("{:3}, ",elem);
std::println();
return out;
}
// Definition der Klasse Matrix
class Matrix
{
short data[SIZE][SIZE]; // Datenfeld
public:
// ctor, belegt Feld mit Zufallszahlen
Matrix()
{
for (auto& row: data)
for (auto& elem: row)
elem = std::rand() % 100;
};
// dtor
~Matrix() = default;
// Matrix mit Vektor multiplizieren
Matrix& operator *= (this Matrix& self, const Vector& vect);
// Ausgabe Daten
friend std::ostream& operator << (std::ostream& out, const Matrix& obj);
};
Matrix& Matrix::operator *= (this Matrix& self, const Vector& vect)
{
for (size_t row=0; row<SIZE; row++)
{
for (size_t col=0; col<SIZE; col++)
self.data[row][col] *= vect.data[row];
}
return self;
}
std::ostream& operator << (std::ostream& out, const Matrix& obj)
{
for (auto& row: obj.data)
{
for (auto& elem: row)
std::print("{:4}, ",elem);
std::println();
}
return out;
}
int main()
{
Matrix mat; // Matrix Objekt und
Vector vect; // Vector Objekt definieren
// Beide Objekte ausgeben
std::println("Matrix: ");
std::cout << mat;
std::println("Vektor: ");
std::cout << vect;
std::println("Matrix *= Vektor:");
mat *= vect;
std::cout << mat;
}
Matrix:
41, 67, 34,
0, 69, 24,
78, 58, 62,
Vektor:
1, 2, 3,
Matrix *= Vektor:
41, 67, 34,
0, 138, 48,
234, 174, 186,
Die friend-Eigenschaft einer Klasse wird niemals vererbt. Würde von der Klasse Matrix eine weitere Klasse SMatrix abgeleitet, hätte SMatrix keinen Zugriff auf die geschützten Eigenschaften von Vector.
Außerdem gilt die friend-Eigenschaft nicht für den Umkehrfall, d.h. ist die Klasse Matrix friend-Klasse von Vector, ist Vector nicht automatisch friend-Klasse von Matrix. Dieser Fall wäre explizit zu spezifizieren.
friend-Funktionen
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([PARAM]);
RTYP ist der Returntyp der Funktion FName. Die Definition der Funktion erfolgt wie gewohnt, d.h. ohne Angabe der friend-Beziehung zu einer Klasse. Aber das sollte bekannt sein.
Bezüglich Ableitung von Klassen und friend-Funktionen gilt das Gleiche wie bei friend-Klassen. Ist eine Funktion Func() friend-Funktion der Klasse Base und ist von Base eine Klasse Sub abgeleitet, ist Func() nicht automatisch friend-Funktion von Sub. Auch dies ist ebenfalls explizit anzugeben.
Und obwohl friend-Funktionen so weit wie möglich vermieden werden sollten, gibt es Fälle, wo es ohne friend-Funktion nicht geht. So wurde z.B. in einem vorangegangenen Kapitel der Plus-Operator '+' der Klasse CString überladen, um folgende Anweisungen schreiben zu können:
stringObj3 = stringObj1 + stringObj2;
stringObj3 = stringObj1 + "any text";
D.h., zu einem CString-Objekt konnte entweder ein CString-Objekt oder ein C-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 '+' als Methode des linken Operanden const char* aufgerufen wird, was die Definition einer Methode unmöglich macht. Damit diese Operation durchgeführt werden kann, wird eine friend-Funktion eingesetzt, die im ersten Parameter den const char-Zeiger erhält und im zweiten Parameter eine Referenz auf das CString-Objekt.
// 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); // copy-ctor
temp = temp + op2; // CString+CString
return temp; // Ergebnis-String zurueck geben
}
Übungen
friend_01:
Das Beispiel zu den friend-Klassen ist wie folgt umzuschreiben:
- Definieren Sie die Klasse Vector nach der Klasse Matrix.
- Löschen Sie in der Klasse Matrix die Definition des Operators *=
- Löschen Sie die friend-Beziehung der Klasse Matrix in der Klasse Vector.
Die Multiplikation der Matrix mit dem Vektor ist in einer Funktion durchzuführen.
Matrix:
41, 67, 34,
0, 69, 24,
78, 58, 62,
Vektor:
1, 2, 3,
Matrix *= Vektor:
41, 67, 34,
0, 138, 48,
234, 174, 186,