Frage Warum erlaubt C ++ keine geerbte Freundschaft?


Warum ist Freundschaft nicht zumindest optional in C ++ vererbbar? Ich verstehe, dass Transitivität und Reflexivität aus offensichtlichen Gründen verboten sind (ich sage dies nur, um einfache Antworten auf die häufig gestellten Fragen zu vermeiden), aber das Fehlen von etwas in der Art von virtual friend class Foo; verwirrt mich. Kennt jemand den historischen Hintergrund dieser Entscheidung? War Freundschaft wirklich nur ein begrenzter Hack, der seitdem seinen Weg in einige obskure respektable Anwendungen gefunden hat?

Zur Klärung bearbeiten: Ich spreche über das folgende Szenario, nicht wo Kinder von A entweder B oder sowohl B als auch seinen Kindern ausgesetzt sind. Ich kann mir auch vorstellen, optional Zugriff auf Überschreibungen von Friend-Funktionen usw. zu gewähren.

class A {
  int x;
  friend class B;
};

class B {
  // OK as per friend declaration above.
  void foo(A& a, int n) { a.x = n; }
};

class D : public B { /* can't get in A w/o 'friend class D' declaration. */ };

Akzeptierte Antwort: wie Loki sagt, der Effekt kann mehr oder weniger simuliert werden, indem geschützte Proxy-Funktionen in gesicherten Basisklassen erstellt werden, also gibt es keine strengen brauchen zum Gewähren von Freundschaft zu einer Klassen- oder virtuellen Methodenhierarchie. Ich mag die Notwendigkeit von Boilerplate-Proxies (die die friended Basis effektiv wird), aber ich nehme an, dass dies gegenüber einem Sprachmechanismus vorzuziehen war, der höchstwahrscheinlich die meiste Zeit missbraucht würde. Ich denke, es ist wahrscheinlich an der Zeit, Stroopstrup zu kaufen und zu lesen Das Design und die Entwicklung von C ++, von denen ich genug Leute hier empfohlen habe, um einen besseren Einblick in diese Art von Fragen zu bekommen ...


75


Ursprung


Antworten:


Weil ich schreiben darf Foo und sein Freund Bar (also gibt es eine Vertrauensbeziehung).

Aber vertraue ich den Leuten, die Klassen schreiben, von denen sie abgeleitet sind Bar?
Nicht wirklich. Sie sollten also keine Freundschaft erben.

Jede Änderung in der internen Repräsentation einer Klasse erfordert eine Änderung an allem, was von dieser Repräsentation abhängt. Somit müssen alle Mitglieder einer Klasse und auch alle Freunde der Klasse geändert werden.

Wenn also die interne Darstellung von Foo ist dann modifiziert Bar muss auch modifiziert werden (weil Freundschaft eng bindet Bar zu Foo). Wenn Freundschaft geerbt wurde, dann wurden alle Klassen abgeleitet Bar wäre auch fest an gebunden Foo und erfordern daher eine Modifikation wenn FooDie interne Darstellung wird geändert. Aber ich habe keine Kenntnis von abgeleiteten Typen (noch sollte ich. Sie können sogar von verschiedenen Firmen usw. entwickelt werden). So könnte ich mich nicht ändern Foo dies würde in der Codebasis zu brechenden Änderungen führen (da ich nicht alle abgeleiteten Klassen ändern konnte) Bar).

Wenn Sie also eine Freundschaft geerbt haben, führen Sie versehentlich eine Einschränkung der Fähigkeit ein, eine Klasse zu ändern. Dies ist unerwünscht, da Sie das Konzept einer öffentlichen API im Grunde nutzlos machen.

Hinweis: Ein Kind von Bar kann Zugreifen Foo durch die Nutzung Barmach einfach die Methode ein Bar geschützt. Dann das Kind von Bar kann auf a zugreifen Foo durch Aufruf über seine Elternklasse.

Ist das was du willst?

class A
{
    int x;
    friend class B;
};

class B
{
    protected:
       // Now children of B can access foo
       void foo(A& a, int n) { a.x = n; }
};

class D : public B
{
    public:
        foo(A& a, int n)
        {
            B::foo(a, n + 5);
        }
};

73



Warum ist Freundschaft nicht zumindest optional in C ++ vererbbar?

Ich denke, dass die Antwort auf Ihre erste Frage in dieser Frage ist: "Haben die Freunde deines Vaters Zugang zu deinen Privatpersonen?"


33



Eine befreundete Klasse kann ihren Freund über Zugriffsfunktionen verfügbar machen und ihnen dann Zugriff gewähren.

class stingy {
    int pennies;
    friend class hot_girl;
};

class hot_girl {
public:
    stingy *bf;

    int &get_cash( stingy &x = *bf ) { return x.pennies; }
};

class moocher {
public: // moocher can access stingy's pennies despite not being a friend
    int &get_cash( hot_girl &x ) { return x.get_cash(); }
};

Dies ermöglicht eine feinere Steuerung als die optionale Transitivität. Beispielsweise, get_cash könnte sein protected oder erzwingt ein Protokoll des laufzeitbegrenzten Zugriffs.


7



C ++ Standard, Abschnitt 11.4 / 8

Freundschaft ist weder vererbt noch transitiv.

Wenn Freundschaft vererbt würde, hätte eine Klasse, die nicht dazu gedacht war, ein Freund zu sein, plötzlich Zugriff auf Ihre Klasseninterna und dies würde die Kapselung verletzen.


7



Weil es einfach unnötig ist.

Die Verwendung der friend Stichwort ist selbst verdächtig. In Bezug auf die Kopplung ist es die schlechteste Beziehung (weit vor Vererbung und Komposition).

Jede Änderung an den Interna einer Klasse hat ein Risiko, die Freunde dieser Klasse zu beeinflussen ... willst du wirklich eine unbekannte Anzahl von Freunden? Sie wären nicht einmal in der Lage, sie aufzulisten, wenn diejenigen, die von ihnen erben, auch Freunde sein könnten, und Sie würden jedes Mal riskieren, den Code Ihres Kunden zu brechen, sicherlich ist dies nicht wünschenswert.

Ich gebe freimütig zu, dass bei Hausaufgaben / Tierprojekten die Abhängigkeit oft eine weit entfernte Überlegung ist. Bei kleinen Projekten ist es egal. Aber sobald mehrere Personen an demselben Projekt arbeiten und dies zu Dutzenden von Tausenden von Linien führt, müssen Sie die Auswirkungen von Änderungen begrenzen.

Dies bringt eine sehr einfache Regel:

Das Ändern der Interna einer Klasse sollte nur die Klasse selbst betreffen

Natürlich wirst du wahrscheinlich seine Freunde beeinflussen, aber es gibt zwei Fälle hier:

  • friend free function: wohl eher eine Mitgliederfunktion (denke ich std::ostream& operator<<(...) hier, der nicht rein zufällig durch die Sprachregeln Mitglied ist
  • Freund Klasse? Sie brauchen keine Freunde in realen Klassen.

Ich würde die Verwendung der einfachen Methode empfehlen:

class Example;

class ExampleKey { friend class Example; ExampleKey(); };

class Restricted
{
public:
  void forExampleOnly(int,int,ExampleKey const&);
};

Das ist einfach Key pattern ermöglicht es Ihnen, einen Freund (in gewisser Weise) zu deklarieren, ohne ihm tatsächlich Zugang zu Ihren Interna zu geben und ihn so von Änderungen zu isolieren. Außerdem kann dieser Freund seinen Schlüssel bei Bedarf an Treuhänder (wie Kinder) verleihen.


2



Eine Vermutung: Wenn eine Klasse eine andere Klasse / Funktion als Freund deklariert, dann deshalb, weil diese zweite Entität einen privilegierten Zugriff auf die erste benötigt. Was nützt es, der zweiten Entität einen privilegierten Zugriff auf eine beliebige Anzahl von Klassen zu gewähren, die von der ersten abgeleitet sind?


0



Eine abgeleitete Klasse kann nur etwas erben, das "Mitglied" der Basis ist. Eine Freundschaftsdeklaration ist nicht ein Mitglied der befreundeten Klasse.

$ 11.4 / 1- "... Der Name eines Freundes ist   nicht im Rahmen der Klasse und der   Freund wird nicht mit dem Mitglied angerufen   Zugriff auf Operatoren (5.2.5), sofern dies nicht der Fall ist   ein Mitglied einer anderen Klasse. "

$ 11.4 - "Auch wegen der Basisklausel   der Freundesklasse gehört nicht dazu   Member-Deklarationen, die Basis-Klausel   der Freundklasse kann nicht auf die   Namen der privaten und geschützten   Mitglieder aus der Klasse gewähren   Freundschaft."

und weiter

$ 10,3 / 7- "[Hinweis: der virtuelle Spezifizierer   impliziert Mitgliedschaft, also eine virtuelle   Funktion kann kein Nichtmitglied sein (7.1.2)   Funktion. Noch kann eine virtuelle Funktion   sei ein statisches Mitglied, da ein virtuelles   Funktionsaufruf beruht auf einem bestimmten   Objekt zur Bestimmung welcher Funktion   aufrufen. Eine virtuelle Funktion wurde deklariert   in einer Klasse kann ein Freund erklärt werden   in einer anderen Klasse. ] "

Da der 'Freund' überhaupt kein Mitglied der Basisklasse ist, wie kann er von der abgeleiteten Klasse übernommen werden?


0



Die Friend-Funktion in einer Klasse weist der Funktion die Eigenschaft extern zu. d. h. extern bedeutet, dass die Funktion irgendwo außerhalb der Klasse deklariert und definiert wurde.

Daher bedeutet es, dass die Friend-Funktion kein Mitglied einer Klasse ist. Mit der Vererbung können Sie also nur die Eigenschaften einer Klasse erben, nicht externe Dinge. Und auch wenn eine Vererbung für Freundesfunktionen erlaubt ist, dann erbt eine dritte Parteiklasse.


0



Friend ist gut in Vererbung wie Stil-Schnittstelle für Container Aber für mich fehlt als erstes C ++ die propagierbare Vererbung

class Thing;

//an interface for Thing container's
struct IThing {
   friend Thing;
   protected:
       int IThing_getData() = 0;
};

//container for thing's
struct MyContainer : public IThing {
    protected: //here is reserved access to Thing
         int IThing_getData() override {...}
};

struct Thing {
    void setYourContainer(IThing* aContainerOfThings) {
        //access to unique function in protected area 
        aContainerOfThings->IThing_getData(); //authorized access
    }
};

struct ChildThing : public Thing {
    void doTest() {
        //here the lack of granularity, you cannot access to the container.
        //to use the container, you must implement all 
        //function in the Thing class
        aContainerOfThings->IThing_getData(); //forbidden access
    }
};

Für mich ist das Problem von C ++ das Fehlen einer sehr guten Granularität Kontrolliere den Zugriff von überall für alles:

Freund Thing kann Freund Thing werden. * Zugang zu allen Kindern von Ding gewähren

Und mehr, Freund [benannter Bereich] Ding. * Zugang für eine genaue gewähren sind in der Container-Klasse über einen speziellen benannten Bereich für den Freund.

Ok, hör auf den Traum. Aber jetzt weißt du eine interessante Verwendung von Freund.

In einer anderen Reihenfolge können Sie auch interessant finden, alle bekannten Klassen sind freundlich mit sich selbst. Mit anderen Worten, eine Klasseninstanz kann alle aufrufen
Mitglieder einer anderen gleichnamigen Instanz ohne Einschränkung:

class Object {
     private:
         void test() {}
     protected:
         void callAnotherTest(Object* anotherObject) {
             //private, but yes you can call test() from 
             //another object instance
             anotherObject)->test(); 
         }
};

0