Wann sollten Sie 'friend' in C ++ verwenden?


354

Ich habe die C ++ - FAQ gelesen und war neugierig auf die friendErklärung. Ich persönlich habe es nie benutzt, aber ich bin daran interessiert, die Sprache zu erkunden.

Was ist ein gutes Beispiel für die Verwendung friend?


Wenn ich die FAQ etwas länger lese, mag ich die Idee, dass der << >>Operator als Freund dieser Klassen überlastet und hinzufügt. Ich bin mir jedoch nicht sicher, wie dies die Kapselung nicht unterbricht. Wann können diese Ausnahmen innerhalb der Strenge bleiben, die OOP ist?


5
Obwohl ich der Antwort zustimme, dass eine Freundesklasse nicht unbedingt eine schlechte Sache ist, neige ich dazu, sie als kleinen Code zu behandeln. Es zeigt oft, wenn auch nicht immer, dass die Klassenhierarchie überdacht werden muss.
Mawg sagt, Monica

1
Sie würden eine Freundesklasse verwenden, in der bereits eine enge Kopplung besteht. Dafür ist es gemacht. Beispielsweise sind eine Datenbanktabelle und ihre Indizes eng miteinander verbunden. Wenn sich eine Tabelle ändert, müssen alle Indizes aktualisiert werden. Die Klasse DBIndex würde DBTable also als Freund deklarieren, damit DBTable direkt auf die Index-Interna zugreifen kann. Es würde jedoch keine öffentliche Schnittstelle zu DBIndex geben. Es macht keinen Sinn, einen Index zu lesen.
shawnhcorey

OOP "Puristen" mit wenig praktischer Erfahrung argumentieren, dass ein Freund gegen die OOP-Prinzipien verstößt, weil eine Klasse der alleinige Erhalter ihres privaten Staates sein sollte. Dies ist in Ordnung, bis Sie auf eine häufige Situation stoßen, in der zwei Klassen einen gemeinsamen privaten Status beibehalten müssen.
Kaalus

Antworten:


335

Erstens (IMO) hören Sie nicht auf Leute, die sagen, dass dies friendnicht nützlich ist. Es ist nützlich. In vielen Situationen verfügen Sie über Objekte mit Daten oder Funktionen, die nicht öffentlich verfügbar sein sollen. Dies gilt insbesondere für große Codebasen mit vielen Autoren, die möglicherweise nur oberflächlich mit verschiedenen Bereichen vertraut sind.

Es gibt Alternativen zum Friend-Bezeichner, aber oft sind sie umständlich (konkrete Klassen auf CPP-Ebene / maskierte Typedefs) oder nicht narrensicher (Kommentare oder Konventionen für Funktionsnamen).

Auf die Antwort;

Der friendBezeichner ermöglicht der angegebenen Klasse den Zugriff auf geschützte Daten oder Funktionen innerhalb der Klasse, die die friend-Anweisung abgeben. Zum Beispiel kann im folgenden Code jeder ein Kind nach seinem Namen fragen, aber nur die Mutter und das Kind dürfen den Namen ändern.

Sie können dieses einfache Beispiel weiterführen, indem Sie eine komplexere Klasse wie ein Fenster betrachten. Sehr wahrscheinlich wird ein Fenster viele Funktions- / Datenelemente haben, die nicht öffentlich zugänglich sein sollten, aber von einer verwandten Klasse wie einem WindowManager benötigt werden.

class Child
{
//Mother class members can access the private parts of class Child.
friend class Mother;

public:

  string name( void );

protected:

  void setName( string newName );
};

114
Als zusätzliche Anmerkung wird in den C ++ - FAQ erwähnt, dass friend die Kapselung verbessert wird . friendgewährt Mitgliedern selektiven Zugang , genau wie dies der protectedFall ist. Eine fein abgestimmte Kontrolle ist besser als die Gewährung eines öffentlichen Zugangs. Andere Sprachen definieren auch selektive Zugriffsmechanismen, berücksichtigen Sie C # internal. Die meiste negative Kritik an der Verwendung von friendbezieht sich auf eine engere Kopplung, was allgemein als eine schlechte Sache angesehen wird. In einigen Fällen ist eine engere Kopplung jedoch genau das, was Sie wollen, und friendgibt Ihnen diese Kraft.
André Caron

5
Könnten Sie bitte mehr über (konkrete Klassen auf CPP-Ebene) und (maskierte Typedefs) Andrew sagen ?
OmarOthman

18
Diese Antwort scheint eher darauf ausgerichtet zu sein, zu erklären, was friendist, als ein motivierendes Beispiel zu liefern . Das Window / WindowManager-Beispiel ist besser als das gezeigte Beispiel, aber zu vage. Diese Antwort behandelt auch nicht den Kapselungsteil der Frage.
Bames53

4
So effektiv existiert 'Freund', weil C ++ keine Vorstellung von einem Paket hat, in dem alle Mitglieder Implementierungsdetails teilen können? Ich würde mich wirklich für ein Beispiel aus der Praxis interessieren.
weberc2

1
Ich finde das Beispiel Mutter / Kind unglücklich. Diese Namen eignen sich für Instanzen, jedoch nicht für Klassen. (Das Problem zeigt, ob wir 2 Mütter haben und jede ihr eigenes Kind hat).
Jo So

162

Bei der Arbeit verwenden wir Freunde, um Code ausgiebig zu testen . Dies bedeutet, dass wir eine ordnungsgemäße Kapselung und Verstecken von Informationen für den Hauptanwendungscode bereitstellen können. Wir können aber auch einen separaten Testcode verwenden, der Freunde verwendet, um den internen Status und die Daten zum Testen zu überprüfen.

Es genügt zu sagen, dass ich das Schlüsselwort friend nicht als wesentlichen Bestandteil Ihres Designs verwenden würde.


Genau dafür benutze ich es. Das oder setzen Sie einfach die Mitgliedsvariablen auf geschützt. Es ist nur eine Schande, dass es für C ++ / CLI nicht funktioniert :-(
Jon Cage

12
Persönlich würde ich davon abraten. In der Regel testen Sie eine Schnittstelle, dh, eine Reihe von Eingaben gibt die erwartete Menge von Ausgaben an. Warum müssen Sie interne Daten überprüfen?
Graeme

55
@Graeme: Weil ein guter Testplan sowohl White-Box- als auch Black-Box-Tests umfasst.
Ben Voigt

1
Ich stimme eher @Graeme zu, wie in dieser Antwort perfekt erklärt .
Alexis Leclerc

2
@ Graeme es kann sein, dass es sich nicht direkt um interne Daten handelt. Möglicherweise handelt es sich um eine Methode, die eine bestimmte Operation oder Aufgabe für diese Daten ausführt, wobei diese Methode für die Klasse privat ist und nicht öffentlich zugänglich sein sollte, während ein anderes Objekt möglicherweise die geschützte Methode dieser Klasse mit eigenen Daten versorgen oder ausstatten muss.
Francis Cugler

93

Das friendSchlüsselwort hat eine Reihe von Verwendungsmöglichkeiten. Hier sind die beiden für mich sofort sichtbaren Verwendungen:

Freund Definition

Mit der Friend-Definition kann eine Funktion im Klassenbereich definiert werden. Die Funktion wird jedoch nicht als Elementfunktion, sondern als freie Funktion des umschließenden Namespace definiert und ist normalerweise nur für die argumentabhängige Suche sichtbar. Das macht es besonders nützlich für die Überlastung des Bedieners:

namespace utils {
    class f {
    private:
        typedef int int_type;
        int_type value;

    public:
        // let's assume it doesn't only need .value, but some
        // internal stuff.
        friend f operator+(f const& a, f const& b) {
            // name resolution finds names in class-scope. 
            // int_type is visible here.
            return f(a.value + b.value);
        }

        int getValue() const { return value; }
    };
}

int main() {
    utils::f a, b;
    std::cout << (a + b).getValue(); // valid
}

Private CRTP-Basisklasse

Manchmal müssen Sie feststellen, dass eine Richtlinie Zugriff auf die abgeleitete Klasse benötigt:

// possible policy used for flexible-class.
template<typename Derived>
struct Policy {
    void doSomething() {
        // casting this to Derived* requires us to see that we are a 
        // base-class of Derived.
        some_type const& t = static_cast<Derived*>(this)->getSomething();
    }
};

// note, derived privately
template<template<typename> class SomePolicy>
struct FlexibleClass : private SomePolicy<FlexibleClass> {
    // we derive privately, so the base-class wouldn't notice that, 
    // (even though it's the base itself!), so we need a friend declaration
    // to make the base a friend of us.
    friend class SomePolicy<FlexibleClass>;

    void doStuff() {
         // calls doSomething of the policy
         this->doSomething();
    }

    // will return useful information
    some_type getSomething();
};

In dieser Antwort finden Sie ein nicht erfundenes Beispiel dafür . Ein anderer Code, der dies verwendet, ist in dieser Antwort enthalten. Die CRTP-Basis wandelt diesen Zeiger um, um mithilfe von Datenelementzeigern auf Datenfelder der abgeleiteten Klasse zugreifen zu können.


Hallo, ich erhalte einen Syntaxfehler (in xcode 4), wenn ich Ihr CRTP ausprobiere. Xcode glaubt, dass ich versuche, eine Klassenvorlage zu erben. Der Fehler tritt bei P<C>in die template<template<typename> class P> class C : P<C> {};besagt , „Verwendung von Klassenvorlage C Vorlage Argumente erfordert“. Haben Sie die gleichen Probleme oder kennen Sie vielleicht eine Lösung?
Bennedich

@bennedich auf den ersten Blick, das sieht nach einem Fehler aus, den Sie bei unzureichender Unterstützung von C ++ - Funktionen erhalten würden. Was unter Compilern ziemlich häufig ist. Die Verwendung von FlexibleClassinnerhalb FlexibleClasssollte sich implizit auf seinen eigenen Typ beziehen.
Yakk - Adam Nevraumont

@bennedich: Die Regeln für die Verwendung des Namens einer Klassenvorlage innerhalb des Klassenkörpers wurden mit C ++ 11 geändert. Versuchen Sie, den C ++ 11-Modus in Ihrem Compiler zu aktivieren.
Ben Voigt

Fügen Sie in Visual Studio 2015 Folgendes hinzu: f () {}; f (int_type t): Wert (t) {}; So verhindern Sie diesen Compilerfehler: Fehler C2440: '<Funktionsstil-Gast>': Kann nicht von 'utils :: f :: int_type' in 'utils :: f' konvertiert werden. Hinweis: Kein Konstruktor konnte den Quelltyp oder Konstruktor übernehmen Überlastungsauflösung war mehrdeutig
Damian

41

@roo : Die Kapselung wird hier nicht unterbrochen, da die Klasse selbst vorschreibt, wer auf ihre privaten Mitglieder zugreifen darf. Die Kapselung würde nur dann unterbrochen, wenn dies von außerhalb der Klasse verursacht werden könnte, z. B. wenn Sie operator <<verkünden würden, dass ich ein Freund der Klasse bin foo.

friendersetzt die Verwendung von public, nicht die Verwendung von private!

Tatsächlich beantworten die C ++ - FAQ dies bereits .


14
"Freund ersetzt die Verwendung von öffentlich, nicht die Verwendung von privat!",
stimme

26
@Assaf: Ja, aber die FQA ist größtenteils eine Menge inkohärenter wütender Kauderwelsche ohne wirklichen Wert. Der Teil auf friendist keine Ausnahme. Die einzige wirkliche Beobachtung hier ist, dass C ++ die Kapselung nur zur Kompilierungszeit sicherstellt. Und Sie brauchen keine Worte mehr, um es zu sagen. Der Rest ist Blödsinn. Zusammenfassend: Dieser Abschnitt der FQA ist nicht erwähnenswert.
Konrad Rudolph

12
Das meiste von dieser FQA ist absolut blx :)
rama-

1
@Konrad: "Die einzige wirkliche Beobachtung hier ist, dass C ++ die Kapselung nur zur Kompilierungszeit sicherstellt." Stellen irgendwelche Sprachen dies zur Laufzeit sicher? Soweit ich weiß, ist die Rückgabe von Verweisen auf private Mitglieder (und Funktionen für Sprachen, die Zeiger auf Funktionen oder Funktionen als erstklassige Objekte zulassen) in C #, Java, Python und vielen anderen zulässig.
André Caron

@ André: Die JVM und die CLR können dies meines Wissens tatsächlich sicherstellen. Ich weiß nicht, ob es immer getan wird, aber Sie können angeblich Pakete / Baugruppen vor solchen Eingriffen schützen (dies habe ich jedoch nie selbst getan).
Konrad Rudolph

27

Das kanonische Beispiel ist das Überladen des Operators <<. Eine weitere häufige Verwendung besteht darin, einem Helfer oder einer Administratorklasse den Zugriff auf Ihre Interna zu ermöglichen.

Hier sind einige Richtlinien, die ich über C ++ - Freunde gehört habe. Der letzte ist besonders denkwürdig.

  • Ihre Freunde sind nicht die Freunde Ihres Kindes.
  • Die Freunde Ihres Kindes sind nicht Ihre Freunde.
  • Nur Freunde können Ihre privaten Teile berühren.

" Das kanonische Beispiel ist das Überladen des Operators <<. " Das kanonische Beispiel, nicht zu verwenden, frienddenke ich.
Neugieriger

16

edit: Die FAQ etwas länger lesen Ich mag die Idee, dass der Operator << >> als Freund dieser Klassen überladen und hinzugefügt wird, bin mir jedoch nicht sicher, wie dies die Kapselung nicht unterbricht

Wie würde es die Kapselung brechen?

Sie unterbrechen die Kapselung, wenn Sie uneingeschränkten Zugriff auf ein Datenelement zulassen . Betrachten Sie die folgenden Klassen:

class c1 {
public:
  int x;
};

class c2 {
public:
  int foo();
private:
  int x;
};

class c3 {
  friend int foo();
private:
  int x;
};

c1ist offensichtlich nicht eingekapselt. Jeder kann es lesen und ändern x. Wir haben keine Möglichkeit, irgendeine Art von Zugangskontrolle durchzusetzen.

c2ist offensichtlich eingekapselt. Es gibt keinen öffentlichen Zugang zu x. Sie können lediglich die fooFunktion aufrufen , die eine sinnvolle Operation für die Klasse ausführt .

c3? Ist das weniger gekapselt? Ermöglicht es uneingeschränkten Zugriff auf x? Ermöglicht es den Zugriff auf unbekannte Funktionen?

Nein. Es ermöglicht genau einer Funktion, auf die privaten Mitglieder der Klasse zuzugreifen. Genau wie c2. Und genau wie c2die einzige Funktion, die Zugriff hat, ist nicht "eine zufällige, unbekannte Funktion", sondern "die in der Klassendefinition aufgeführte Funktion". Genauso c2können wir anhand der Klassendefinitionen eine vollständige Liste der Personen sehen, die Zugriff haben.

Wie genau ist das weniger gekapselt? Die gleiche Menge an Code hat Zugriff auf die privaten Mitglieder der Klasse. Und jeder , der Zugriff hat, ist in der Klassendefinition aufgeführt.

friendbricht die Kapselung nicht. Einige Java-Programmierer fühlen sich unwohl, denn wenn sie "OOP" sagen, meinen sie tatsächlich "Java". Wenn sie „Encapsulation“ sagen, meinen sie nicht, sondern „eine Java - Klasse , wo die einzigen Funktionen der Lage , den Zugang private Mitglieder, sind die Teilnehmer“ „private Mitglieder müssen vor willkürlichen Zugriffen geschützt werden“, auch wenn dies völliger Unsinn ist für mehrere Gründe .

Erstens ist es, wie bereits gezeigt, zu einschränkend. Es gibt keinen Grund, warum Freundmethoden nicht dasselbe tun dürfen.

Zweitens ist es nicht restriktiv genug . Betrachten Sie eine vierte Klasse:

class c4 {
public:
  int getx();
  void setx(int x);
private:
  int x;
};

Dies ist gemäß der oben genannten Java-Mentalität perfekt gekapselt. Und doch erlaubt es absolut jedem, x zu lesen und zu modifizieren . Wie macht das überhaupt Sinn? (Hinweis: Nicht)

Fazit: Bei der Kapselung geht es darum, steuern zu können, welche Funktionen auf private Mitglieder zugreifen können. Es geht nicht genau darum, wo sich die Definitionen dieser Funktionen befinden.


10

Eine weitere gängige Version von Andrews Beispiel ist das gefürchtete Code-Couplet

parent.addChild(child);
child.setParent(parent);

Anstatt sich Sorgen zu machen, ob beide Zeilen immer zusammen und in konsistenter Reihenfolge ausgeführt werden, können Sie die Methoden privat machen und eine Friend-Funktion haben, um die Konsistenz zu erzwingen:

class Parent;

class Object {
private:
    void setParent(Parent&);

    friend void addChild(Parent& parent, Object& child);
};

class Parent : public Object {
private:
     void addChild(Object& child);

     friend void addChild(Parent& parent, Object& child);
};

void addChild(Parent& parent, Object& child) {
    if( &parent == &child ){ 
        wetPants(); 
    }
    parent.addChild(child);
    child.setParent(parent);
}

Mit anderen Worten, Sie können die öffentlichen Schnittstellen kleiner halten und Invarianten erzwingen, die Klassen und Objekte in Friend-Funktionen überschneiden.


6
Warum sollte jemand dafür einen Freund brauchen? Warum nicht die addChildMitgliedsfunktion auch das übergeordnete Element festlegen lassen?
Nawaz

1
Ein besseres Beispiel wäre machen setParenteinen Freund, wie Sie Clients wollen , damit die Eltern sich ändern , da Sie es in der Verwaltung werde addChild/ removeChildKategorie von Funktionen.
Ylisar

8

Sie steuern die Zugriffsrechte für Mitglieder und Funktionen mit privaten / geschützten / öffentlichen Rechten? Wenn also die Idee jeder dieser drei Ebenen klar ist, sollte klar sein, dass uns etwas fehlt ...

Die Deklaration eines Mitglieds / einer Funktion als geschützt ist beispielsweise ziemlich allgemein. Sie sagen, dass diese Funktion für alle unerreichbar ist (außer natürlich für ein geerbtes Kind). Aber was ist mit Ausnahmen? Mit jedem Sicherheitssystem können Sie eine Art "weiße Liste" haben, oder?

Mit Friend haben Sie die Flexibilität, eine solide Objektisolierung zu haben, aber es kann eine "Lücke" für Dinge geschaffen werden, die Sie für gerechtfertigt halten.

Ich denke, die Leute sagen, dass es nicht benötigt wird, weil es immer ein Design gibt, das ohne es auskommt. Ich denke, es ist ähnlich wie bei der Diskussion globaler Variablen: Sie sollten sie niemals verwenden. Es gibt immer einen Weg, auf sie zu verzichten. In Wirklichkeit sehen Sie jedoch Fälle, in denen dies der (fast) eleganteste Weg ist. Ich denke, das ist der gleiche Fall mit Freunden.

Es nützt nichts, außer dass Sie auf eine Mitgliedsvariable zugreifen können, ohne eine Einstellungsfunktion zu verwenden

Nun, das ist nicht genau die Art, es zu betrachten. Die Idee ist zu kontrollieren, wer auf etwas zugreifen kann, ob eine Einstellungsfunktion wenig damit zu tun hat oder nicht .


2
Wie ist friendeine Lücke? Damit können die in der Klasse aufgeführten Methoden auf ihre privaten Mitglieder zugreifen. Es kann immer noch kein beliebiger Code darauf zugreifen. Als solches unterscheidet es sich nicht von einer öffentlichen Mitgliederfunktion.
Jalf

Freund ist so nah wie möglich am Zugriff auf C # / Java-Paketebene in C ++. @jalf - was ist mit Freundklassen (wie einer Fabrikklasse)?
Oger Psalm33

1
@Ogre: Was ist mit ihnen? Sie geben dieser Klasse immer noch speziell und niemand sonst Zugriff auf die Interna der Klasse. Sie lassen nicht nur das Tor offen, damit beliebiger unbekannter Code mit Ihrer Klasse verschraubt werden kann.
Jalf

8

Ich fand einen praktischen Ort, um den Zugang von Freunden zu nutzen: Unittest von privaten Funktionen.


Aber kann dafür auch eine öffentliche Funktion genutzt werden? Was ist der Vorteil der Verwendung des Freundeszugriffs?
Zheng Qu

@ Maverobot Könnten Sie Ihre Frage näher erläutern?
VladimirS

5

Friend ist praktisch, wenn Sie einen Container erstellen und einen Iterator für diese Klasse implementieren möchten.


4

Wir hatten ein interessantes Problem bei einer Firma, bei der ich zuvor gearbeitet habe, bei der wir Freunde für anständige Affekte eingesetzt haben. Ich habe in unserer Framework-Abteilung gearbeitet. Wir haben ein grundlegendes System auf Engine-Ebene über unser benutzerdefiniertes Betriebssystem erstellt. Intern hatten wir eine Klassenstruktur:

         Game
        /    \
 TwoPlayer  SinglePlayer

Alle diese Klassen waren Teil des Rahmens und wurden von unserem Team gepflegt. Die von der Firma produzierten Spiele wurden auf diesem Framework aufgebaut, das von einem der Games-Kinder stammt. Das Problem war, dass Game Schnittstellen zu verschiedenen Dingen hatte, auf die SinglePlayer und TwoPlayer Zugriff benötigen, die wir jedoch nicht außerhalb der Framework-Klassen verfügbar machen wollten. Die Lösung bestand darin, diese Schnittstellen privat zu machen und TwoPlayer und SinglePlayer über eine Freundschaft auf sie zuzugreifen.

Ehrlich gesagt hätte dieses ganze Problem durch eine bessere Implementierung unseres Systems gelöst werden können, aber wir waren an das gebunden, was wir hatten.


4

Die kurze Antwort wäre: Freund benutzen, wenn es sich tatsächlich verbessert Kapselung . Die Verbesserung der Lesbarkeit und Benutzerfreundlichkeit (Operatoren << und >> sind das kanonische Beispiel) ist ebenfalls ein guter Grund.

Beispiele für die Verbesserung der Kapselung sind Klassen, die speziell für die Zusammenarbeit mit den Interna anderer Klassen entwickelt wurden (Testklassen fallen mir ein).


" Operatoren << und >> sind das kanonische Beispiel " Nein. Eher kanonische Gegenbeispiele .
Neugieriger

@curiousguy: Operatoren <<und >>sind normalerweise Freunde anstelle von Mitgliedern, da die Verwendung zu Mitgliedern die Verwendung umständlich macht. Ich spreche natürlich von dem Fall, in dem diese Betreiber auf private Daten zugreifen müssen. Ansonsten ist Freundschaft nutzlos.
Gorpik

Weil sie Mitglieder machen würde sie umständlich machen zu können. “ Offensichtlich machen operator<<und operator>>Mitglieder der Wertklasse statt Nicht-Mitglieder oder Mitglieder i|ostream, würden die gewünschte Syntax nicht zur Verfügung stellen, und ich bin nicht darauf hindeutet es. " Ich spreche von dem Fall, in dem diese Betreiber auf private Daten zugreifen müssen. " Ich verstehe nicht ganz, warum die Eingabe- / Ausgabeoperatoren auf private Mitglieder zugreifen müssen.
Neugieriger

4

Der Schöpfer von C ++ sagt, dass dies kein Kapselungsprinzip vermittelt, und ich werde ihn zitieren:

Verstößt "Freund" gegen die Kapselung? Nein, tut es nicht. "Freund" ist genau wie die Mitgliedschaft ein expliziter Mechanismus, um Zugriff zu gewähren. Sie können sich (in einem standardkonformen Programm) keinen Zugriff auf eine Klasse gewähren, ohne deren Quelle zu ändern.

Ist mehr als klar ...


@curiousguy: Auch bei Vorlagen ist es wahr.
Nawaz

@Nawaz Template Freundschaft kann gewährt werden, aber jeder kann eine neue teilweise oder explizite Spezialisierung vornehmen, ohne die Klasse zu ändern, die Freundschaft gewährt. Aber seien Sie vorsichtig mit ODR-Verstößen, wenn Sie das tun. Und mach das sowieso nicht.
Neugieriger

3

Eine andere Verwendung: Freund (+ virtuelle Vererbung) kann verwendet werden, um das Ableiten von einer Klasse zu vermeiden (auch bekannt als "eine Klasse unteraktivierbar machen") => 1 , 2

Ab 2 :

 class Fred;

 class FredBase {
 private:
   friend class Fred;
   FredBase() { }
 };

 class Fred : private virtual FredBase {
 public:
   ...
 }; 

3

Um TDD oft zu machen, habe ich in C ++ das Schlüsselwort 'friend' verwendet.

Kann ein Freund alles über mich wissen?


Aktualisiert: Ich habe diese wertvolle Antwort zum Schlüsselwort "Freund" von der Bjarne Stroustrup-Website gefunden .

"Freund" ist genau wie die Mitgliedschaft ein expliziter Mechanismus, um Zugriff zu gewähren.


3

Sie müssen sehr vorsichtig sein, wann / wo Sie das friendSchlüsselwort verwenden, und wie Sie habe ich es sehr selten verwendet. Nachfolgend finden Sie einige Hinweise zur Verwendung friendund zu den Alternativen.

Angenommen, Sie möchten zwei Objekte vergleichen, um festzustellen, ob sie gleich sind. Sie könnten entweder:

  • Verwenden Sie Accessor-Methoden, um den Vergleich durchzuführen (überprüfen Sie jeden Ivar und bestimmen Sie die Gleichheit).
  • Sie können auch direkt auf alle Mitglieder zugreifen, indem Sie sie öffentlich machen.

Das Problem mit der ersten Option ist, dass dies eine Menge Accessoren sein können, die (etwas) langsamer als der direkte variable Zugriff, schwerer zu lesen und umständlich sind. Das Problem beim zweiten Ansatz besteht darin, dass Sie die Kapselung vollständig unterbrechen.

Was schön wäre, wäre, wenn wir eine externe Funktion definieren könnten, die immer noch Zugriff auf die privaten Mitglieder einer Klasse erhalten könnte. Wir können dies mit dem friendSchlüsselwort tun :

class Beer {
public:
    friend bool equal(Beer a, Beer b);
private:
    // ...
};

Das Verfahren equal(Beer, Beer)hat nun direkten Zugriff auf aund b‚s private Mitglieder (was sein kann char *brand, float percentAlcoholusw. Dies ist ein ziemlich konstruiertes Beispiel, Sie früher anwenden würde , friendum eine überladene == operator, aber wir werden auf das bekommen.

Ein paar Dinge zu beachten:

  • A friendist KEINE Mitgliedsfunktion der Klasse
  • Es ist eine gewöhnliche Funktion mit besonderem Zugang zu den privaten Mitgliedern der Klasse
  • Ersetzen Sie nicht alle Accessoren und Mutatoren durch Freunde (Sie können auch alles machen public!)
  • Freundschaft ist nicht wechselseitig
  • Freundschaft ist nicht transitiv
  • Freundschaft wird nicht vererbt
  • Oder, wie in den C ++ - FAQ erklärt : "Nur weil ich Ihnen Freundschaftszugriff auf mich erteile, gewähren Sie Ihren Kindern nicht automatisch Zugriff auf mich, gewähren Ihren Freunden nicht automatisch Zugriff auf mich und gewähren mir nicht automatisch Zugriff auf mich." . "

Ich benutze friendses nur dann wirklich, wenn es viel schwieriger ist, es andersherum zu machen. Als weiteres Beispiel sind viele Vektor - Mathematik - Funktionen oft erstellt friendsaufgrund der Interoperabilität Mat2x2, Mat3x3, Mat4x4, Vec2, Vec3, Vec4, etc. Und es ist nur so viel einfacher , Freunde zu sein, anstatt zu haben Accessoren überall zu nutzen. Wie bereits erwähnt, friendist es oft nützlich, wenn es auf das <<(sehr praktisch zum Debuggen) >>und möglicherweise auf den ==Operator angewendet wird, kann aber auch für Folgendes verwendet werden:

class Birds {
public:
    friend Birds operator +(Birds, Birds);
private:
    int numberInFlock;
};


Birds operator +(Birds b1, Birds b2) {
    Birds temp;
    temp.numberInFlock = b1.numberInFlock + b2.numberInFlock;
    return temp;
}

Wie gesagt, ich benutze es nicht friendsehr oft, aber hin und wieder ist es genau das, was du brauchst. Hoffe das hilft!


2

In Bezug auf Operator << und Operator >> gibt es keinen guten Grund, diese Operatoren zu Freunden zu machen. Es ist wahr, dass sie keine Mitgliedsfunktionen sein sollten, aber sie müssen auch keine Freunde sein.

Am besten erstellen Sie öffentliche Druck- (ostream &) und Lesefunktionen (istream &). Schreiben Sie dann den Operator << und den Operator >> in Bezug auf diese Funktionen. Dies bietet den zusätzlichen Vorteil, dass Sie diese Funktionen virtuell gestalten können, was eine virtuelle Serialisierung ermöglicht.


" In Bezug auf Operator << und Operator >> gibt es keinen guten Grund, diese Operatoren zu Freunden zu machen. " Absolut korrekt. " Dies bietet den zusätzlichen Vorteil, dass Sie diese Funktionen virtuell gestalten können. " Wenn die betreffende Klasse zur Ableitung vorgesehen ist, ja. Warum sonst die Mühe machen?
Neugieriger

Ich verstehe wirklich nicht, warum diese Antwort zweimal abgelehnt wurde - und das ohne eine Erklärung! Das ist unhöflich.
Neugieriger

Virtual würde einen Perf-Hit hinzufügen, der in der Serialisierung ziemlich groß sein könnte
Paulm

2

Ich verwende das Freund-Schlüsselwort nur, um geschützte Funktionen zu testen. Einige werden sagen, dass Sie geschützte Funktionen nicht testen sollten. Ich finde dieses Tool jedoch sehr nützlich, wenn ich neue Funktionen hinzufüge.

Ich verwende das Schlüsselwort jedoch nicht direkt in den Klassendeklarationen, sondern verwende einen raffinierten Template-Hack, um dies zu erreichen:

template<typename T>
class FriendIdentity {
public:
  typedef T me;
};

/**
 * A class to get access to protected stuff in unittests. Don't use
 * directly, use friendMe() instead.
 */
template<class ToFriend, typename ParentClass>
class Friender: public ParentClass
{
public:
  Friender() {}
  virtual ~Friender() {}
private:
// MSVC != GCC
#ifdef _MSC_VER
  friend ToFriend;
#else
  friend class FriendIdentity<ToFriend>::me;
#endif
};

/**
 * Gives access to protected variables/functions in unittests.
 * Usage: <code>friendMe(this, someprotectedobject).someProtectedMethod();</code>
 */
template<typename Tester, typename ParentClass>
Friender<Tester, ParentClass> & 
friendMe(Tester * me, ParentClass & instance)
{
    return (Friender<Tester, ParentClass> &)(instance);
}

Dies ermöglicht mir Folgendes:

friendMe(this, someClassInstance).someProtectedFunction();

Funktioniert mindestens mit GCC und MSVC.


2

In C ++ ist das Schlüsselwort "friend" beim Überladen von Operatoren und beim Erstellen von Bridge hilfreich.

1.) Friend-Schlüsselwort beim Überladen von Operatoren:
Beispiel für das Überladen von Operatoren ist: Angenommen, wir haben eine Klasse "Point" mit zwei Gleitkommavariablen
"x" (für x-Koordinate) und "y" (für y-Koordinate). Jetzt müssen wir überladen "<<"(Extraktionsoperator), so dass, wenn wir aufrufen"cout << pointobj" die x- und y-Koordinate gedruckt wird (wobei pointobj ein Objekt der Klasse Point ist). Dazu haben wir zwei Möglichkeiten:

   1.Überladen Sie die Funktion "operator << ()" in der Klasse "ostream".
   2.Überladen Sie die Funktion "operator << ()" in der Klasse "Point".
Jetzt ist die erste Option nicht gut, denn wenn wir diesen Operator für eine andere Klasse erneut überladen müssen, müssen wir erneut Änderungen in der "ostream" -Klasse vornehmen.
Deshalb ist die zweite Option die beste. Jetzt kann der Compiler die "operator <<()"Funktion aufrufen :

   1.Verwenden des ostream-Objekts cout.As: cout.operator << (Pointobj) (Form ostream class). 
2. Anruf ohne Objekt. As: Operator << (cout, Pointobj) (aus der Point-Klasse).

Weil wir eine Überladung in der Point-Klasse implementiert haben. Um diese Funktion ohne Objekt aufzurufen, müssen wir ein "friend"Schlüsselwort hinzufügen, da wir eine Friend-Funktion ohne Objekt aufrufen können. Jetzt lautet die Funktionsdeklaration wie folgt:
"friend ostream &operator<<(ostream &cout, Point &pointobj);"

2.) Schlüsselwort Friend bei der Erstellung einer Brücke:
Angenommen, wir müssen eine Funktion erstellen, in der wir auf ein privates Mitglied von zwei oder mehr Klassen zugreifen müssen (im Allgemeinen als "Brücke" bezeichnet). Vorgehensweise:
Um auf ein privates Mitglied einer Klasse zugreifen zu können, sollte es Mitglied dieser Klasse sein. Um nun auf ein privates Mitglied einer anderen Klasse zugreifen zu können, sollte jede Klasse diese Funktion als Freundfunktion deklarieren. Beispiel: Angenommen, es gibt zwei Klassen A und B. Eine Funktion "funcBridge()"möchte auf ein privates Mitglied beider Klassen zugreifen. Dann sollten beide Klassen deklarieren "funcBridge()"als:
friend return_type funcBridge(A &a_obj, B & b_obj);

Ich denke, dies würde helfen, das Schlüsselwort "Freund" zu verstehen.


2

Wie die Referenz für die Erklärung eines Freundes sagt:

Die Freunddeklaration wird in einem Klassenkörper angezeigt und gewährt einer Funktion oder einer anderen Klasse Zugriff auf private und geschützte Mitglieder der Klasse, in der die Freunddeklaration angezeigt wird.

Nur zur Erinnerung, es gibt technische Fehler in einigen der Antworten, die besagen, dass friendnur geschützte Mitglieder besucht werden können.


1

Das Baumbeispiel ist ein ziemlich gutes Beispiel: Ein Objekt in einer anderen Klasse implementieren zu lassen, ohne eine Vererbungsbeziehung zu haben.

Vielleicht brauchen Sie es auch, um einen Konstruktor zu schützen und die Leute zu zwingen, Ihre "Freund" -Fabrik zu benutzen.

... Ok, ehrlich gesagt kannst du ohne leben.


1

Um TDD oft zu machen, habe ich in C ++ das Schlüsselwort 'friend' verwendet.
Kann ein Freund alles über mich wissen?

Nein, es ist nur eine Einbahnstraße: `(


1

Eine spezielle Instanz, die ich verwende, friendist das Erstellen von Singleton- Klassen. Mit dem friendSchlüsselwort kann ich eine Accessor-Funktion erstellen, die präziser ist als immer eine "GetInstance ()" - Methode für die Klasse.

/////////////////////////
// Header file
class MySingleton
{
private:
    // Private c-tor for Singleton pattern
    MySingleton() {}

    friend MySingleton& GetMySingleton();
}

// Accessor function - less verbose than having a "GetInstance()"
//   static function on the class
MySingleton& GetMySingleton();


/////////////////////////
// Implementation file
MySingleton& GetMySingleton()
{
    static MySingleton theInstance;
    return theInstance;
}

Dies mag Geschmackssache sein, aber ich denke nicht, dass das Speichern einiger Tastenanschläge die Verwendung eines Freundes hier rechtfertigt. GetMySingleton () sollte eine statische Methode der Klasse sein.
Gorpik

Der private C-Tor würde eine Nicht-Freund-Funktion zum Instanziieren von MySingleton nicht zulassen, daher wird hier das Schlüsselwort friend benötigt.
JBRWilkinson

@Gorpik " Das mag Geschmackssache sein, aber ich glaube nicht, dass das Speichern einiger Tastenanschläge die Verwendung eines Freundes hier rechtfertigt. " Wie auch immer, friendist nicht eine bestimmte „Rechtfertigung“ muß, wenn eine Member - Funktion Hinzufügen nicht.
Neugieriger

Singletons werden ohnehin als schlechte Praxis angesehen (Google "Singleton schädlich" und Sie werden viele Ergebnisse wie diese erhalten . Ich denke nicht, dass die Verwendung einer Funktion zum Implementieren eines Antimusters als eine gute Verwendung dieser Funktion angesehen werden kann.
weberc2

1

Friend-Funktionen und -Klassen bieten direkten Zugriff auf private und geschützte Mitglieder der Klasse, um zu vermeiden, dass die Kapselung im allgemeinen Fall unterbrochen wird. Die meiste Verwendung erfolgt mit ostream: Wir möchten Folgendes eingeben können:

Point p;
cout << p;

Dies kann jedoch den Zugriff auf die privaten Daten von Point erfordern, daher definieren wir den überladenen Operator

friend ostream& operator<<(ostream& output, const Point& p);

Es gibt jedoch offensichtliche Auswirkungen auf die Verkapselung. Erstens hat die Freundesklasse oder -funktion jetzt vollen Zugriff auf ALLE Mitglieder der Klasse, auch auf diejenigen, die nicht ihren Anforderungen entsprechen. Zweitens sind die Implementierungen der Klasse und des Freundes jetzt so eng miteinander verbunden, dass eine interne Änderung in der Klasse den Freund beschädigen kann.

Wenn Sie den Freund als Erweiterung der Klasse betrachten, ist dies logischerweise kein Problem. Aber warum war es in diesem Fall überhaupt notwendig, den Freund auszuspießen?

Um dasselbe zu erreichen, was "Freunde" vorgeben zu erreichen, ohne jedoch die Kapselung zu unterbrechen, kann man dies tun:

class A
{
public:
    void need_your_data(B & myBuddy)
    {
        myBuddy.take_this_name(name_);
    }
private:
    string name_;
};

class B
{
public:
    void print_buddy_name(A & myBuddy)
    {
        myBuddy.need_your_data(*this);
    }
    void take_this_name(const string & name)
    {
        cout << name;
    }
}; 

Die Kapselung ist nicht unterbrochen, Klasse B hat keinen Zugriff auf die interne Implementierung in A, aber das Ergebnis ist das gleiche, als hätten wir B als Freund von A deklariert. Der Compiler optimiert die Funktionsaufrufe, sodass dies dasselbe ergibt Anweisungen als direkter Zugriff.

Ich denke, die Verwendung von "Freund" ist einfach eine Abkürzung mit fraglichem Nutzen, aber bestimmten Kosten.


1

Dies ist möglicherweise keine tatsächliche Anwendungsfallsituation, kann jedoch dazu beitragen, die Verwendung von Freunden zwischen Klassen zu veranschaulichen.

Das Clubhaus

class ClubHouse {
public:
    friend class VIPMember; // VIP Members Have Full Access To Class
private:
    unsigned nonMembers_;
    unsigned paidMembers_;
    unsigned vipMembers;

    std::vector<Member> members_;
public:
    ClubHouse() : nonMembers_(0), paidMembers_(0), vipMembers(0) {}

    addMember( const Member& member ) { // ...code }   
    void updateMembership( unsigned memberID, Member::MembershipType type ) { // ...code }
    Amenity getAmenity( unsigned memberID ) { // ...code }

protected:
    void joinVIPEvent( unsigned memberID ) { // ...code }

}; // ClubHouse

Die Mitgliederklasse

class Member {
public:
    enum MemberShipType {
        NON_MEMBER_PAID_EVENT,   // Single Event Paid (At Door)
        PAID_MEMBERSHIP,         // Monthly - Yearly Subscription
        VIP_MEMBERSHIP,          // Highest Possible Membership
    }; // MemberShipType

protected:
    MemberShipType type_;
    unsigned id_;
    Amenity amenity_;
public:
    Member( unsigned id, MemberShipType type ) : id_(id), type_(type) {}
    virtual ~Member(){}
    unsigned getId() const { return id_; }
    MemberShipType getType() const { return type_; }
    virtual void getAmenityFromClubHouse() = 0       
};

class NonMember : public Member {
public:
   explicit NonMember( unsigned id ) : Member( id, MemberShipType::NON_MEMBER_PAID_EVENT ) {}   

   void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }
};

class PaidMember : public Member {
public:
    explicit PaidMember( unsigned id ) : Member( id, MemberShipType::PAID_MEMBERSHIP ) {}

    void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }
};

class VIPMember : public Member {
public:
    friend class ClubHouse;
public:
    explicit VIPMember( unsigned id ) : Member( id, MemberShipType::VIP_MEMBERSHIP ) {}

    void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }

    void attendVIPEvent() {
        ClubHouse::joinVIPEvent( this->id );
    }
};

Ausstattung

class Amenity{};

Wenn Sie sich hier die Beziehung dieser Klassen ansehen; Das ClubHouse bietet verschiedene Arten von Mitgliedschaften und Zugang zu Mitgliedern. Die Mitglieder werden alle von einer Super- oder Basisklasse abgeleitet, da sie alle eine gemeinsame ID und einen Aufzählungstyp gemeinsam haben und externe Klassen über Zugriffsfunktionen in der Basisklasse auf ihre IDs und Typen zugreifen können.

Aufgrund dieser Art von Hierarchie der Mitglieder und ihrer abgeleiteten Klassen und ihrer Beziehung zur ClubHouse-Klasse ist die VIPMember-Klasse die einzige der abgeleiteten Klassen, die "besondere Berechtigungen" besitzt. Die Basisklasse und die anderen beiden abgeleiteten Klassen können nicht auf die joinVIPEvent () -Methode des ClubHouse zugreifen. Die VIP-Mitgliedsklasse verfügt jedoch über dieses Privileg, als hätte sie vollständigen Zugriff auf dieses Ereignis.

Mit dem VIPMember und dem ClubHouse ist es also eine Einbahnstraße in beide Richtungen, in der die anderen Mitgliederklassen begrenzt sind.


0

Bei der Implementierung von Baumalgorithmen für Klassen hatte der Framework-Code, den der Professor uns gegeben hatte, die Baumklasse als Freund der Knotenklasse.

Es nützt nichts, außer dass Sie auf eine Mitgliedsvariable zugreifen können, ohne eine Einstellungsfunktion zu verwenden.


0

Sie können Freundschaft verwenden, wenn verschiedene Klassen (die nicht voneinander erben) private oder geschützte Mitglieder der anderen Klasse verwenden.

Typische Anwendungsfälle von Friend-Funktionen sind Vorgänge, die zwischen zwei verschiedenen Klassen ausgeführt werden, die auf private oder geschützte Mitglieder beider zugreifen.

von http://www.cplusplus.com/doc/tutorial/inheritance/ .

Sie können dieses Beispiel sehen, in dem eine Nichtmitgliedsmethode auf die privaten Mitglieder einer Klasse zugreift. Diese Methode muss in dieser Klasse als Freund der Klasse deklariert werden.

// friend functions
#include <iostream>
using namespace std;

class Rectangle {
    int width, height;
  public:
    Rectangle() {}
    Rectangle (int x, int y) : width(x), height(y) {}
    int area() {return width * height;}
    friend Rectangle duplicate (const Rectangle&);
};

Rectangle duplicate (const Rectangle& param)
{
  Rectangle res;
  res.width = param.width*2;
  res.height = param.height*2;
  return res;
}

int main () {
  Rectangle foo;
  Rectangle bar (2,3);
  foo = duplicate (bar);
  cout << foo.area() << '\n';
  return 0;
}

0

Wahrscheinlich habe ich etwas von den obigen Antworten verpasst, aber ein weiteres wichtiges Konzept bei der Kapselung ist das Verstecken der Implementierung. Das Reduzieren des Zugriffs auf private Datenelemente (die Implementierungsdetails einer Klasse) ermöglicht später eine wesentlich einfachere Änderung des Codes. Wenn ein Freund direkt auf die privaten Daten zugreift und alle Änderungen an den Implementierungsdatenfeldern (privaten Daten) vorgenommen werden, wird der Code, der auf diese Daten zugreift, unterbrochen. Die Verwendung von Zugriffsmethoden beseitigt dies größtenteils. Ziemlich wichtig würde ich denken.


-1

Sie können die strengsten und reinsten OOP-Prinzipien einhalten und sicherstellen, dass keine Datenelemente für eine Klasse über Zugriffsmethoden verfügen, sodass alle Objekte die einzigen sein müssen , die über ihre Daten Bescheid wissen können. Die einzige Möglichkeit, auf sie zu reagieren, sind indirekte Nachrichten , sind . dh Methoden.

Aber auch C # hat ein internes Sichtbarkeitsschlüsselwort und Java hat für einige Dinge die Standardzugriffsfähigkeit auf Paketebene . C ++ kommt dem OOP-Ideal tatsächlich näher, indem der Kompromiss der Sichtbarkeit in eine Klasse minimiert wird, indem genau angegeben wird , welche andere Klasse und nur andere Klassen in sie hinein sehen können.

Ich benutze C ++ nicht wirklich, aber wenn C # Freunde hätte, würde ich das anstelle des Assembly-globalen internen Modifikators tun , den ich tatsächlich oft benutze. Die Kapselung wird nicht wirklich unterbrochen, da die Bereitstellungseinheit in .NET ist eine Montage.

Aber dann gibt es das InternalsVisibleTo- Attribut (otherAssembly), das sich wie ein montageübergreifender Freundschaftsmechanismus verhält . Microsoft verwendet dies für visuelle Designer- Assemblys.


-1

Freunde sind auch nützlich für Rückrufe. Sie können Rückrufe als statische Methoden implementieren

class MyFoo
{
private:
    static void callback(void * data, void * clientData);
    void localCallback();
    ...
};

wo callbackAnrufe localCallbackintern und dieclientData hat Ihre Instanz drin. Meiner Meinung nach,

oder...

class MyFoo
{
    friend void callback(void * data, void * callData);
    void localCallback();
}

Dies ermöglicht es dem Freund, rein im cpp als c-artige Funktion definiert zu werden und die Klasse nicht zu überladen.

Ebenso ist ein Muster, das ich sehr oft gesehen habe, alles wirklich zu sagen privaten Mitglieder einer Klasse in eine andere Klasse zu setzen, die im Header deklariert, im cpp definiert und befreundet ist. Dies ermöglicht es dem Codierer, einen Großteil der Komplexität und internen Arbeitsweise der Klasse vor dem Benutzer des Headers zu verbergen.

In der Kopfzeile:

class MyFooPrivate;
class MyFoo
{
    friend class MyFooPrivate;
public:
    MyFoo();
    // Public stuff
private:
    MyFooPrivate _private;
    // Other private members as needed
};

In der cpp,

class MyFooPrivate
{
public:
   MyFoo *owner;
   // Your complexity here
};

MyFoo::MyFoo()
{
    this->_private->owner = this;
}

Es wird einfacher, Dinge zu verbergen, die der Downstream nicht so sehen muss.


1
Wären Schnittstellen nicht ein sauberer Weg, um dies zu erreichen? Was kann jemanden davon abhalten, MyFooPrivate.h nachzuschlagen?
JBRWilkinson

1
Wenn Sie privat und öffentlich Geheimnisse bewahren, werden Sie leicht besiegt. Mit "Verstecken" meine ich, dass der Benutzer von MyFoo die privaten Mitglieder nicht wirklich sehen muss. Außerdem ist es nützlich, die ABI-Kompatibilität aufrechtzuerhalten. Wenn Sie _private zu einem Zeiger machen, kann sich die private Implementierung beliebig ändern, ohne die öffentliche Schnittstelle zu berühren, wodurch die ABI-Kompatibilität erhalten bleibt.
Shash

Sie beziehen sich auf die PIMPL-Sprache. Der Zweck, für den keine zusätzliche Kapselung verwendet wird, wie Sie zu sagen scheinen, sondern das Verschieben der Implementierungsdetails aus dem Header, sodass das Ändern eines Implementierungsdetails keine Neukompilierung des Clientcodes erzwingt. Außerdem muss kein Freund verwendet werden, um diese Redewendung zu implementieren.
weberc2

Nun ja. Der Hauptzweck besteht darin, Implementierungsdetails zu verschieben. Der Freund dort ist nützlich, um private Mitglieder innerhalb der öffentlichen Klasse von privat oder umgekehrt zu behandeln.
Shash
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.