C++-Programmierung/ Eigene Datentypen definieren/ Freunde
Normalerweise können Member einer Klasse, die als private
deklariert wurden, nicht von außen zugegriffen werden. In einigen Fällen kann es aber sinnvoll sein, wenn bestimmte Funktionen und Klassen (direkten) Zugriff auf die Member einer Klasse haben. Anwendungsbeispiele finden sich oft bei der Überladung von Operatoren, die als Nicht-Member implementiert werden müssen, da der erste Parameter kein Objekt der Klasse selbst sein soll.
Funktionen als Freunde
BearbeitenEine Funktion kann als Freund deklariert werden, indem in der Klassendefinition das Schlüsselwort friend
gefolgt vom Prototyp der Funktion angegeben wird. Für Methoden einer anderen Klasse muss dem Funktionsnamen, durch den Bereichsoperator (::
) getrennt, der Klassenname vorangestellt werden. Analoges gilt für Funktionen aus einem anderen Namensraum. Für die Angabe von friend
ist es unerheblich, in welchem Zugriffsbereich (public
oder private
) die Funktion deklariert wurde. Üblicherweise werden friend
-Deklarationen am Anfang oder Ende der Klassendefinition vorgenommen.
#include <iostream>
class A; // Klasse A deklariert
namespace super{
void f2(A const& a); // Deklaration
}
class B{ // Klasse B definiert
public:
B(A const& a):a_(a){}
void f3(); // Deklaration
private:
A const& a_;
};
class A{ // Klasse A definiert
public:
A():x_(5){} // Standardkonstruktor
private:
int x_;
// "Meine Freunde sind:"
friend void f1(A const&); // die nicht zu dieser Klasse gehörende, globale Funktion 'f1',
friend void super::f2(A const&); // die Funktion 'f2' aus dem Namespace 'super',
friend void B::f3(); // die Methode 'f3' der Klasse 'B'.
};
// Definitionen (A musste erst definiert werden)
void f1(A const& a){
std::cout << "f1: " << a.x_ << std::endl;
}
namespace super{
void f2(A const& a){
std::cout << "f2: " << a.x_ << std::endl;
}
}
void B::f3(){
std::cout << "f3: " << a_.x_ << std::endl;
}
int main(){
A a;
B b = a;
f1(a);
super::f2(a);
b.f3();
}
f1: 5
f2: 5
f3: 5
Wie Sie sehen muss einiger Aufwand getrieben werden, um die verschiedenen Deklarationen und Definitionen in die richtige Reihenfolge zu bringen. Da alle drei Funktionen (f1
, f2
, f3
) auf einen Member von A
zugreifen, muss als Erstes eine Deklaration der Klasse A
vorgenommen werden. Ohne eine vollständige Definition von A
kann man jedoch noch nicht auf die Member zugreifen, da diese noch nicht bekannt sind. Eine Definition von A
ist an dieser Stelle wiederum noch nicht möglich, da für friend
-Deklarationen in der Regel eine vorherige Deklaration der Funktionen nötig ist. Die Ausnahme bildet in diesem Fall die Funktion f1
, die im gleichen Namensbereich (dem globalen) wie A
deklariert wird. Hier ist eine implizite Deklaration bei der friend
-Deklarationen zulässig. Folglich müssen also zunächst die Deklarationen von f2
im Namensbereich super
und von f3
als Teil der Definition der Klasse B
erfolgen. Anschließend ist eine Definition von A
möglich und danach auch die Definition der drei Funktionen.
Eine Stelle, wo friend
s oft vorkommen, sind die Überladungen für die Ausgabe-/Eingabeoperatoren, sprich <<
und >>
. Sie müssen meistens als Freunde deklariert werden.
Klassen als Freunde
BearbeitenWenn eine ganze Klasse als Freund deklariert werden soll, wird friend
gefolgt von dem Schlüsselwort class
und anschließend dem Klassennamen angegeben. Alle Methoden einer befreundeten Klasse sind somit als friend
deklariert. Es ist aber nur selten sinnvoll dies zu tun, da nur die nötigsten externen Funktionen Zugriff auf das Innenleben einer Klasse haben sollten. Wie im echten Leben ist Freundschaft nicht automatisch beidseitig. Wenn also in einer Klasse eine andere als friend
deklariert wird, heißt dies keineswegs, dass auch die andere Klasse der ersten friend
-Zugriffe einräumt.
#include <iostream>
class B; // Deklaration von B
class A{ // Definition von A
public:
A():a_(5){}
void f(B const& b); // Deklaration
private:
int a_;
};
class B{ // Definition von B
public:
B():b_(42){}
void f(A const& a); // Deklaration
private:
int b_;
friend class A;
};
void A::f(B const& b){
std::cout << "A: B glaubt, ich sei sein Freund: ";
std::cout << b.b_ << std::endl; // OK: A ist Freund von B
}
void B::f(A const& a){
std::cout << "B: A betrachtet mich nicht als Freund." << std::endl;
// std::cout << a.a_ << std::endl; // Fehler: B ist kein Freund von A
}
int main(){
A a;
B b;
a.f(b);
b.f(a);
}
A: B glaubt, ich sei sein Freund: 42
B: A betrachtet mich nicht als Freund.
Würde eine Methode von B
versuchen, auf einen privaten Member von A
zuzugreifen, würde der Compiler einen Fehler melden. Umgekehrt wurde in B
explizit gestattet, dass Methoden von A
auf private Member von B
zugreifen dürfen.