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 FreundeBearbeiten

Eine 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();
}
Ausgabe:
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 friends oft vorkommen, sind die Überladungen für die Ausgabe-/Eingabeoperatoren, sprich << und >>. Sie müssen meistens als Freunde deklariert werden.

Klassen als FreundeBearbeiten

Wenn 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);
}
Ausgabe:
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.