Warum setzen wir private Member-Funktionen in Header?

16

Die Antwort auf die Frage, warum wir private Membervariablen in C ++ - Header einfügen, lautet, dass die Größe der Klasse an den Stellen bekannt sein muss, an denen Instanzen deklariert werden, damit der Compiler Code generieren kann, der sich entsprechend im Stapel bewegt.

Warum müssen wir private Mitglieder in Überschriften setzen?

Aber gibt es einen Grund, private Funktionen in der Klassendefinition zu deklarieren?

Die Alternative wäre im Wesentlichen das Pimpl-Idiom, jedoch ohne die überflüssige Indirektion.

Ist diese Sprachfunktion mehr als ein historischer Fehler?

Praxeolitische
quelle

Antworten:

11

Private Member-Funktionen können sein virtual, und in allgemeinen Implementierungen von C ++ (die eine vtable verwenden) müssen alle Clients der Klasse die spezifische Reihenfolge und Anzahl der virtuellen Funktionen kennen. Dies gilt auch dann, wenn eine oder mehrere der virtuellen Mitgliedsfunktionen vorhanden sind private.

Es könnte den Anschein haben, als würde man "den Wagen vor das Pferd legen", da die Auswahl der Compiler-Implementierung die Sprachspezifikation nicht beeinflussen sollte. In Wirklichkeit wurde die C ++ - Sprache jedoch gleichzeitig mit einer funktionierenden Implementierung ( Cfront ) entwickelt, die vtables verwendete.

Greg Hewgill
quelle
4
"Implementierungsentscheidungen sollten sich nicht auf die Sprache auswirken" ist fast das genaue Gegenteil des C ++ Mantras. Die Spezifikation schreibt keine bestimmte Implementierung vor, aber es gibt viele Regeln, die speziell auf die Anforderungen einer besonders effizienten Implementierungsauswahl zugeschnitten sind.
Ben Voigt
Das klingt sehr plausibel. Gibt es eine Quelle dazu?
Praxeolitic
@Praxeolitic: Sie können über die Virtual Method Table lesen .
Greg Hewgill
1
@Praxeolitic - finden Sie eine alte Kopie des 'The Annotated C ++ Refernce Manual' ( stroustrup.com/arm.html ) - die Autoren sprechen über verschiedene Implementierungsdetails und wie es die Sprache geprägt hat.
Michael Kohne
Ich bin mir nicht sicher, ob dies die Frage wirklich beantwortet. Für mich spricht es nur eine bestimmte Facette an - wenn auch gut - und lässt den Rest hinter sich: Warum müssen wir nicht-virtuelle Funktionen für private Mitglieder in Kopfzeilen einfügen? Sicher, nur aus Gründen der Konsistenz / Einfachheit ist ein Argument - aber im Idealfall hätten wir auch eine mechanistische und / oder philosophische Begründung. Deshalb habe ich eine Antwort hinzugefügt, die meiner Meinung nach dies als eine sehr bewusste Designauswahl mit sehr vorteilhaften Effekten erklärt.
Underscore_d
13

Wenn Sie zulassen, dass Methoden zu einer Klasse außerhalb ihrer Definition hinzugefügt werden , können sie von jedem an einer beliebigen Stelle in einer beliebigen Datei hinzugefügt werden .

Dies würde jedem Client-Code sofort trivialen Zugriff auf private und geschützte Datenelemente gewähren.

Sobald Sie die Klassendefinition abgeschlossen haben, können Sie einige Dateien nicht mehr als vom Autor besonders gesegnet markieren, um sie zu erweitern. Es gibt nur flache Übersetzungseinheiten. Die einzige vernünftige Möglichkeit, dem Compiler mitzuteilen, dass eine bestimmte Gruppe von Methoden offiziell ist oder vom Klassenautor gesegnet wurde, besteht darin, sie innerhalb der Klasse zu deklarieren.


Beachten Sie, dass wir in C ++ direkten Speicherzugriff haben. Das bedeutet, dass es im Allgemeinen trivial ist, einen Schattentyp mit demselben Speicherlayout wie Ihre Klasse zu erstellen, meine eigenen Methoden hinzuzufügen (oder einfach alle Daten zu veröffentlichen) und reinterpret_cast. Oder ich kann den Code Ihrer privaten Funktion finden oder zerlegen. Oder suchen Sie die Funktionsadresse in der Symboltabelle und rufen Sie oder direkt auf.

Diese Zugriffsspezifizierer versuchen nicht, diese Angriffe zu verhindern, da dies nicht möglich ist. Sie geben nur an, wie eine Klasse verwendet werden soll.

Nutzlos
quelle
1
Die Klassendefinition könnte sich auf eine Funktionscontainer-Entität beziehen, die vor dem Clientcode verborgen ist. Alternativ könnte dies invertiert werden und eine vollständige traditionelle Klassendefinition, die vor dem Clientcode verborgen ist, könnte eine sichtbare Schnittstelle bestimmen, die nur öffentliche Mitglieder beschreibt und deren Größe offenbaren kann.
Praxeolitic
1
Sicher, aber das Kompilierungsmodell bietet keine vernünftige Möglichkeit, um zu verhindern, dass Client-Code eine eigene Version des verborgenen Containers oder eine andere sichtbare Schnittstelle mit zusätzlichen Zugriffsmechanismen einfügt.
Nutzlos
Das ist ein guter Punkt, aber gilt das nicht für die Definitionen aller Member-Funktionen, die ohnehin nicht im Header enthalten sind?
Praxeolitic
1
Alle Member-Funktionen werden jedoch innerhalb der Klasse deklariert . Der Punkt ist, dass Sie keine neuen Elementfunktionen hinzufügen können, die nicht mindestens in der Klassendefinition deklariert wurden (und die Regel für eine Definition verhindert mehrere Definitionen).
Nutzlos
Was ist dann mit einem Container mit privaten Funktionen?
Praxeolitic
8

Die akzeptierte Antwort erklärt dies für virtuelle private Funktionen, aber das beantwortet nur eine bestimmte Facette der Frage, die wesentlich begrenzter ist als die, die das OP gestellt hat. Wir müssen also umformulieren: Warum müssen wir nicht-virtuelle private Funktionen in Headern deklarieren ?

Eine andere Antwort ruft die Tatsache hervor, dass Klassen in einem Block deklariert werden müssen - danach sind sie versiegelt und können nicht hinzugefügt werden. Das ist, was Sie tun würden, indem Sie weglassen, eine private Methode im Header zu deklarieren, und dann versuchen, sie an anderer Stelle zu definieren. Schöner Punkt. Warum sollten einige Benutzer der Klasse in der Lage sein, sie auf eine Weise zu erweitern, die andere Benutzer nicht beobachten können? Private Methoden gehören dazu und sind nicht davon ausgeschlossen. Aber dann fragst du, warum sie enthalten sind, und es scheint ein bisschen tautologisch. Warum müssen Klassenbenutzer über sie Bescheid wissen? Wenn sie nicht sichtbar wären, könnten Benutzer keine hinzufügen, und hey presto.

Daher wollte ich eine Antwort geben, die nicht nur private Methoden standardmäßig einbezieht, sondern bestimmte Punkte für die Sichtbarkeit für Benutzer vorsieht. Ein mechanistischer Grund für nicht-virtuelle private Funktionen, die eine öffentliche Erklärung erfordern, wird in Herb Sutters GotW # 100 über die Pimpl-Redewendung als Teil seiner Begründung angegeben. Ich werde hier nicht weiter auf Pimpl eingehen, da wir alle sicher davon wissen. Aber hier ist der relevante Punkt:

In C ++ müssen alle Benutzer dieser Klasse neu kompiliert werden, wenn sich etwas in der Klassendefinition einer Headerdatei ändert. Dies gilt auch dann, wenn nur die privaten Klassenmitglieder geändert wurden, auf die die Benutzer der Klasse nicht einmal zugreifen können. Dies liegt daran, dass das Build-Modell von C ++ auf der Einbeziehung von Text basiert und C ++ davon ausgeht, dass Aufrufer zwei wichtige Dinge über eine Klasse wissen, die von privaten Mitgliedern beeinflusst werden können:

  • Größe und Layout : [von Mitgliedern und virtuellen Funktionen - selbsterklärend und großartig für die Leistung, aber nicht, warum wir hier sind]
  • Funktionen : Der aufrufende Code muss in der Lage sein, Aufrufe an Mitgliedsfunktionen der Klasse aufzulösen, einschließlich nicht zugreifbarer privater Funktionen, die mit nicht privaten Funktionen überladen sind. Wenn die private Funktion besser übereinstimmt, kann der aufrufende Code nicht kompiliert werden. (C ++ entschied sich aus Sicherheitsgründen absichtlich für die Überlastungslösung, bevor die Barrierefreiheit überprüft wurde. Beispielsweise wurde die Ansicht vertreten, dass die Änderung der Barrierefreiheit einer Funktion von privat zu öffentlich die Bedeutung des legalen Aufrufcodes nicht ändern sollte.)

Sutter ist natürlich eine äußerst zuverlässige Quelle als Mitglied des Komitees, daher kennt er "eine bewusste Entwurfsentscheidung", wenn er eine sieht. Und die Idee, eine öffentliche Deklaration privater Methoden zu fordern, um eine veränderte Semantik oder eine versehentlich unterbrochene Zugänglichkeit später zu vermeiden, ist wahrscheinlich die überzeugendste Begründung. Zum Glück, denn das Ganze schien bisher ziemlich sinnlos zu sein!

underscore_d
quelle
" Aber dann fragst du warum, und diese Antwort scheint tautologisch zu sein. Warum müssen die Benutzer der Klasse wieder über sie Bescheid wissen? " Nein, es ist nicht tautologisch. Benutzer müssen nicht unbedingt "über sie Bescheid wissen". Was passieren muss, ist, dass Sie in der Lage sein müssen, Personen daran zu hindern, Klassen ohne die Zustimmung des Klassenautors zu erweitern. Alle diese Mitglieder müssen also von vornherein deklariert werden. Dass dies dazu führt, dass Benutzer über private Mitglieder informiert werden, ist lediglich eine notwendige Konsequenz.
Nicol Bolas
1
@NicolBolas Sicher. Das war für mich wahrscheinlich nicht sehr gut formuliert. Ich habe gemeint, dass die Antwort nur die Sichtbarkeit privater Methoden als Folge einer (sehr gültigen) Regel erklärt, die viele Dinge abdeckt, anstatt eine Begründung für die Sichtbarkeit privater Methoden zu liefern. Die eigentliche Antwort ist natürlich eine Kombination aus Nutzlosen, Gnasher und meinen. Ich möchte nur eine andere Perspektive bieten, die noch nicht hier war.
Underscore_d
4

Dafür gibt es zwei Gründe.

Stellen Sie zunächst fest, dass der Zugriffsspezifizierer für den Compiler bestimmt und zur Laufzeit nicht relevant ist. Der Zugriff auf ein privates Mitglied außerhalb des Gültigkeitsbereichs ist ein Kompilierungsfehler .

Prägnanz

Betrachten Sie eine Funktion, die kurz ist, eine oder zwei Zeilen. Es ist vorhanden, um die Replikation von Code an anderer Stelle zu reduzieren, was auch den Vorteil hat, dass geändert werden kann, wie ein Algorithmus oder was auch immer an einem Ort statt an vielen anderen funktioniert (z. B. Ändern eines Sortieralgorithmus).

Möchten Sie lieber eine kurze ein oder zwei Zeile in der Kopfzeile haben, oder haben Sie den Funktionsprototyp dort und irgendwo eine Implementierung? Es ist einfacher im Header zu finden, und für kurze Funktionen ist es weitaus ausführlicher, eine separate Implementierung zu haben.

Es gibt einen weiteren großen Vorteil, nämlich ...

Inline-Funktionen

Eine private Funktion kann möglicherweise inline gesetzt werden, und dies erfordert zwangsläufig, dass sie sich im Header befindet. Bedenken Sie:

class A {
  private:
    inline void myPrivateFunction() {
      ...
    }

  public:
    inline void somePublicFunction() {
      myPrivateFunction();
      ...
    }
};

Die private Funktion kann möglicherweise zusammen mit der öffentlichen Funktion eingeblendet werden. Dies liegt im Ermessen des Compilers, da das inlineSchlüsselwort technisch gesehen ein Vorschlag und keine Anforderung ist.

Gemeinschaft
quelle
Alle innerhalb des Klassenkörpers definierten Funktionen werden automatisch markiert inline, es gibt keinen Grund, das Schlüsselwort dort zu verwenden.
Ben Voigt
@BenVoigt so ist es. Ich habe das nicht bemerkt und benutze C ++ seit einiger Zeit in Teilzeit. Diese Sprache überrascht mich immer wieder mit solchen kleinen Nuggets.
Ich glaube nicht, dass eine Methode in der Kopfzeile stehen muss, um eingebunden zu werden. Es muss sich nur in derselben Zusammenstellungseinheit befinden. Gibt es einen Fall, in dem es sinnvoll ist, separate Zusammenstellungseinheiten für eine Klasse zu haben?
Samuel Danielson
@SamuelDanielson richtig, aber eine private Funktion muss in der Klassendefinition enthalten sein. Der Begriff "privat" impliziert, dass er Teil der Klasse ist. Es wäre möglich, eine Nichtmitgliedsfunktion in der .cppDatei zu haben, die von Mitgliedsfunktionen umrahmt wird, die außerhalb der Klassendefinition definiert sind, aber eine solche Funktion wäre nicht privat.
Der Kern der Frage war: "Gibt es einen Grund , private Funktionen in der Klassendefinition zu deklarieren ?". Sie sprechen über die Definition der privaten Funktionen. Dies beantwortet die Frage nicht. @SamuelDanielson Heutzutage bedeutet LTO, dass Funktionen überall in einem Projekt vorhanden sein können und die gleiche Chance haben, eingebunden zu werden. Wenn Sie eine Klasse in mehrere Übersetzungseinheiten aufteilen, ist der einfachste Fall, dass die Klasse nur groß ist und Sie sie semantisch in mehrere Quelldateien aufteilen möchten. Ich bin mir sicher, dass solch große Klassen entmutigt sind, aber trotzdem
underscore_d
2

Ein weiterer Grund für die Verwendung privater Methoden in der Header-Datei: In einigen Fällen kann eine öffentliche Inline-Methode nur einen oder mehrere private Methoden aufrufen. Wenn die privaten Methoden im Header enthalten sind, kann ein Aufruf der öffentlichen Methode vollständig in den tatsächlichen Code der privaten Methoden eingefügt werden, und Inlining hört nicht mit einem Aufruf der privaten Methode auf. Auch von einer anderen Kompilierungseinheit (und öffentliche Methoden würden normalerweise von verschiedenen Kompilierungseinheiten aufgerufen).

Natürlich gibt es auch den Grund, warum der Compiler Probleme mit der Überladungsauflösung nicht erkennen kann, wenn er nicht alle Methoden kennt, einschließlich der privaten.

gnasher729
quelle
Hervorragender Punkt zum Inlining! Ich stelle mir vor, dass dies für die stdlib und andere Bibliotheken mit inline definierten Methoden besonders relevant ist. (wenn nicht so sehr für internen Code, wo LTO fast alles über TU-Grenzen hinweg tun kann)
underscore_d
0

Damit können diese Funktionen auf die privaten Mitglieder zugreifen. Ansonsten müsstest du friendsie sowieso in der Kopfzeile eintragen.

Wenn irgendeine Funktion auf die privaten Mitglieder der Klasse zugreifen könnte, wäre private nutzlos.

Ratschenfreak
quelle
1
Vielleicht hätte ich in der Frage genauer sein sollen - ich meinte, warum ist die Sprache so gestaltet? Warum muss es sein oder warum ist dieses Design gut?
Praxeolitic