Warum erlaubt C ++ keine ererbte Freundschaft?

92

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

Zur Verdeutlichung bearbeiten: Ich spreche über das folgende Szenario, nicht wenn Kinder von A entweder B oder sowohl B als auch seinen Kindern ausgesetzt sind. Ich kann mir auch vorstellen, optional Zugriff auf Überschreibungen von Freundfunktionen 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 feststellt, kann der Effekt mehr oder weniger simuliert werden, indem geschützte Proxy-Funktionen in befreundeten Basisklassen ausgeführt werden. Daher besteht keine strikte Notwendigkeit , einer Klassen- oder virtuellen Methodenerbschaft Freundschaft zu gewähren. Ich mag die Notwendigkeit von Boilerplate-Proxys (die die befreundete Basis effektiv wird) nicht, aber ich nehme an, dass dies einem Sprachmechanismus vorzuziehen war, der mit größerer Wahrscheinlichkeit die meiste Zeit missbraucht wird. Ich denke, es ist wahrscheinlich an der Zeit, Stroupstrups The Design and Evolution of C ++ zu kaufen und zu lesen , das ich hier genug empfohlen habe, um einen besseren Einblick in diese Art von Fragen zu erhalten ...

Jeff
quelle

Antworten:

93

Weil ich schreiben darf Foound 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 Darstellung einer Klasse erfordert eine Änderung an allem, was von dieser Darstellung abhängig ist. Daher müssen alle Mitglieder einer Klasse und auch alle Freunde der Klasse geändert werden.

Deshalb , wenn die interne Darstellung Foodann geändert wird , Barmuß ebenfalls modifiziert werden (weil Freundschaft fest bindet Baran Foo). Wenn die Freundschaft vererbt Barwürde, wäre auch jede abgeleitete Klasse eng an sie gebunden Foound müsste daher Foogeändert werden, wenn die interne Darstellung geändert wird. Aber ich habe keine Kenntnis von abgeleiteten Typen (und sollte ich auch nicht. Sie können sogar von verschiedenen Firmen usw. entwickelt werden). Daher wäre ich nicht in der Lage, Änderungen Foovorzunehmen, da dies zu Änderungen in der Codebasis führen würde (da ich nicht alle von abgeleiteten Klassen ändern könnte Bar).

Wenn also eine Freundschaft geerbt wurde, 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 grundsätzlich unbrauchbar machen.

Hinweis: Ein Kind von Barkann Foomithilfe von zugreifen Bar, machen Sie die Methode einfach Bargeschützt. Dann kann das Kind von Barauf a zugreifen, Fooindem es über seine Elternklasse aufruft.

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);
        }
};
Loki Astari
quelle
4
Bingo. Es geht darum, den Schaden zu begrenzen, den Sie durch Ändern der Interna einer Klasse verursachen können.
j_random_hacker
Ehrlich gesagt ist der Fall, über den ich wirklich nachdenke, das Anwalts-Mandanten-Muster, bei dem ein Zwischenprodukt als eingeschränkte Schnittstelle zu externen Klassen fungiert, indem es der zugrunde liegenden Klasse mit eingeschränktem Zugriff Wrapper-Methoden präsentiert. Zu sagen, dass eine Schnittstelle allen Kindern anderer Klassen zur Verfügung steht und nicht einer genauen Klasse, wäre derzeit viel nützlicher als das System.
Jeff
@Jeff: Wenn die interne Darstellung allen untergeordneten Elementen einer Klasse zur Verfügung gestellt wird, wird der Code unveränderlich (dies unterbricht auch die Kapselung, da jeder, der auf die internen Mitglieder zugreifen möchte, nur von Bar erben muss, auch wenn er nicht wirklich eine Bar ist ).
Martin York
@Martin: Richtig, in diesem Schema könnte die befreundete Basis verwendet werden, um zur befreundeten Klasse zu gelangen, was in vielen (wenn nicht den meisten) Fällen eine einfache Verletzung der Kapselung sein könnte. In Situationen, in denen die befreundete Basis eine abstrakte Klasse ist, würde jede abgeleitete Klasse gezwungen sein, eine eigene wechselseitige Schnittstelle zu implementieren. Ich bin mir nicht sicher, ob eine "Betrüger" -Klasse in diesem Szenario als Verstoß gegen die Kapselung oder als Verstoß gegen einen Schnittstellenvertrag angesehen wird, wenn sie nicht versucht hat, ihre beanspruchte Rolle ordnungsgemäß zu handeln.
Jeff
@Martin: Richtig, das ist der Effekt, den ich möchte und manchmal sogar tatsächlich verwende, wobei A tatsächlich eine wechselseitig befreundete Schnittstelle zu einer eingeschränkten Zugriffsklasse Z ist. Die häufigste Beschwerde mit der normalen Anwalts-Mandanten-Redewendung scheint zu sein, dass die Schnittstellenklasse A muss Call-Wrapper auf Z übertragen, und um den Zugriff auf Unterklassen zu erweitern, muss das Boilerplate von A im Wesentlichen in jeder Basisklasse wie B dupliziert werden. Eine Schnittstelle drückt normalerweise aus, welche Funktionalität ein Modul bieten möchte, nicht welche Funktionalität in anderen will sich nutzen.
Jeff
47

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

Ich denke, die Antwort auf Ihre erste Frage lautet: "Haben die Freunde Ihres Vaters Zugang zu Ihren Privaten?"

wilx
quelle
36
Um fair zu sein, wirft diese Frage beunruhigende Fragen über Ihren Vater auf. . .
Iheanyi
3
Was ist der Sinn dieser Antwort? bestenfalls ein zweifelhafter, wenn auch möglicherweise unbeschwerter Kommentar
DeveloperChris
10

Eine befreundete Klasse kann ihren Freund durch Accessor-Funktionen entlarven und dann über diese 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. Zum Beispiel get_cashkann protectedoder kann ein Protokoll der Laufzeit begrenzten Zugangs erzwingen.

Kartoffelklatsche
quelle
@Hector: Abstimmung für Refactoring! ;)
Alexander Shukaev
7

C ++ Standard, Abschnitt 11.4 / 8

Freundschaft ist weder vererbt noch transitiv.

Wenn Freundschaft vererbt würde, hätte eine Klasse, die nicht als Freund gedacht war, plötzlich Zugriff auf Ihre Klasseninternalen und dies verstößt gegen die Kapselung.

David
quelle
2
Sagen Sie Q "Freunde" A, und B wird von A abgeleitet. Wenn B die Freundschaft von A erbt, dann ist es technisch gesehen ein A, das Zugriff auf Qs Privaten hat, da B ein Typ von A ist. Dies beantwortet die Frage also nicht mit einem praktischen Grund.
Mdenton8
2

Weil es einfach unnötig ist.

Die Verwendung des friendSchlüsselworts ist selbst verdächtig. In Bezug auf die Kopplung ist es die schlechteste Beziehung (weit vor Vererbung und Zusammensetzung).

Jede Änderung an den Interna einer Klasse kann sich auf die Freunde dieser Klasse auswirken. Möchten Sie wirklich eine unbekannte Anzahl von Freunden? Sie könnten sie nicht einmal auflisten, wenn diejenigen, die von ihnen erben, auch Freunde sein könnten, und Sie laufen Gefahr, jedes Mal den Code Ihres Kunden zu brechen. Dies ist sicherlich nicht wünschenswert.

Ich gebe frei zu, dass bei Hausaufgaben- / Haustierprojekten die Abhängigkeit oft eine weit entfernte Überlegung ist. Bei kleinen Projekten spielt es keine Rolle. Sobald jedoch mehrere Personen an demselben Projekt arbeiten und dies zu Zehntausenden von Zeilen wird, müssen Sie die Auswirkungen von Änderungen begrenzen.

Dies bringt eine sehr einfache Regel:

Das Ändern der Interna einer Klasse sollte sich nur auf die Klasse selbst auswirken

Natürlich werden Sie wahrscheinlich seine Freunde betreffen, aber hier gibt es zwei Fälle:

  • Freund freie Funktion: wahrscheinlich sowieso eher eine Mitgliedsfunktion (ich denke std::ostream& operator<<(...)hier, die kein Mitglied rein zufällig der Sprachregeln ist
  • Freund Klasse? Du brauchst keine Freundschaftsklassen für echte 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&);
};

Mit diesem einfachen KeyMuster können Sie einen Freund (in gewisser Weise) deklarieren, ohne ihm tatsächlich Zugriff auf Ihre Interna zu gewähren, und ihn so von Änderungen isolieren. Darüber hinaus kann dieser Freund bei Bedarf seinen Schlüssel an Treuhänder (wie Kinder) verleihen.

Matthieu M.
quelle
0

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

Oliver Charlesworth
quelle
2
Wenn eine Klasse A B und ihren Nachkommen Freundschaft gewähren wollte, konnte sie vermeiden, ihre Schnittstelle für jede hinzugefügte Unterklasse zu aktualisieren oder B zu zwingen, ein Durchgangs-Boilerplate zu schreiben, was meiner Meinung nach in erster Linie der halbe Punkt der Freundschaft ist.
Jeff
@ Jeff: Ah, dann habe ich deine beabsichtigte Bedeutung falsch verstanden. Ich nahm an, Sie meinten, Bdass Zugriff auf alle Klassen haben würde, die von A...
Oliver Charlesworth
0

Eine abgeleitete Klasse kann nur etwas erben, das 'Mitglied' der Basis ist. Eine Freundeserklärung ist kein Mitglied der Freundschaftsklasse.

$ 11.4 / 1- "... Der Name eines Freundes gehört nicht zum Geltungsbereich der Klasse, und der Freund wird nicht mit den Mitgliedszugriffsoperatoren (5.2.5) aufgerufen, es sei denn, er ist Mitglied einer anderen Klasse."

$ 11.4 - "Da die Basisklausel der Freundesklasse nicht Teil ihrer Mitgliederdeklarationen ist, kann die Basisklausel der Freundklasse nicht auf die Namen der privaten und geschützten Mitglieder der Klasse zugreifen, die Freundschaft gewährt."

und weiter

$ 10.3 / 7- "[Hinweis: Der virtuelle Bezeichner impliziert eine Mitgliedschaft, sodass eine virtuelle Funktion keine Nichtmitgliedsfunktion (7.1.2) sein kann. Eine virtuelle Funktion kann auch kein statisches Mitglied sein, da ein virtueller Funktionsaufruf auf einem bestimmten Objekt für beruht Bestimmen, welche Funktion aufgerufen werden soll. Eine in einer Klasse deklarierte virtuelle Funktion kann in einer anderen Klasse als Freund deklariert werden.] "

Wie kann der 'Freund', da er überhaupt kein Mitglied der Basisklasse ist, von der abgeleiteten Klasse geerbt werden?

Chubsdad
quelle
Freundschaft, obwohl durch Erklärungen wie Mitglieder gegeben, sind nicht wirklich Mitglieder, sondern Benachrichtigungen, von denen andere Klassen die Sichtbarkeitsklassifikationen für "echte" Mitglieder im Wesentlichen ignorieren können. Während in den von Ihnen zitierten Spezifikationsabschnitten erläutert wird, wie die Sprache in Bezug auf diese Nuancen und das Frame-Verhalten in einer selbstkonsistenten Terminologie funktioniert, könnten die Dinge anders gestaltet worden sein, und leider bringt nichts darüber die Begründung auf den Punkt.
Jeff
0

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

Daher bedeutet dies, dass die Freundesfunktion kein Mitglied einer Klasse ist. Mit der Vererbung können Sie also nur die Eigenschaften einer Klasse erben, nicht externe Dinge. Und auch wenn die Vererbung für Friend-Funktionen zulässig ist, erbt eine Klasse von Drittanbietern.

Robert
quelle
0

Freund ist gut in der Vererbung wie Stilschnittstelle für Container Aber für mich, wie das erste sagen, fehlt 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, um den gesamten Zugriff von überall für irgendetwas zu steuern:

Freund Sache kann Freund Sache werden. *, um allen Kindern von Sache Zugang zu gewähren

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

Ok, hör auf mit dem Traum. Aber jetzt kennen Sie eine interessante Verwendung von Freund.

In einer anderen Reihenfolge finden Sie auch interessant zu wissen, dass alle Klassen mit sich selbst befreundet sind. Mit anderen Worten, eine Klasseninstanz kann alle
Mitglieder einer anderen Instanz mit demselben Namen ohne Einschränkung aufrufen :

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

Einfache Logik: „Ich habe eine Freundin Jane. Nur weil wir gestern Freunde geworden sind, gehören nicht alle ihre Freunde mir. '

Ich muss diese einzelnen Freundschaften noch genehmigen, und das Maß an Vertrauen wäre dementsprechend.

Robert Hamm
quelle