it-swarm.com.de

Warum müssen wir private Mitglieder in Header setzen?

Private Variablen sind eine Möglichkeit, dem Benutzer einer Klasse Komplexität und Implementierungsdetails zu verbergen. Dies ist eine ziemlich nette Funktion. Aber ich verstehe nicht, warum wir sie in c ++ in den Header einer Klasse setzen müssen. Ich sehe zwei ärgerliche Nachteile:

  • Es überhäuft den Header vom Benutzer
  • Es erzwingt die Neukompilierung aller Clientbibliotheken, wenn die Interna geändert werden

Gibt es einen konzeptionellen Grund für diese Anforderung? Ist es nur, um die Arbeit des Compilers zu erleichtern?

67
Simon Bergot

Dies liegt daran, dass der C++ - Compiler die tatsächliche Größe der Klasse kennen muss, um bei der Instanziierung die richtige Speichermenge zuzuweisen. Und die Größe umfasst alle Mitglieder, auch private.

Eine Möglichkeit, dies zu vermeiden, ist die Verwendung des Pimpl-Idioms , das von Herb Sutter in seiner Guru of the Week-Reihe # 24 und erklärt wurde # 28 .

Aktualisieren

In der Tat ist dies (oder allgemeiner die Unterscheidung zwischen Header- und Quelldatei und #includes) ist eine große Hürde in C++, die von C geerbt wurde C++ C wurde erstellt, es gab noch keine Erfahrung mit der Entwicklung von Software in großem Maßstab, wo dies zu echten Problemen führt. Die seitdem gewonnenen Erkenntnisse wurden von Designern neuerer Sprachen beachtet, aber C++ ist an Abwärtskompatibilitätsanforderungen gebunden, was es wirklich schwierig macht, ein so grundlegendes Problem in der Sprache anzugehen.

75
Péter Török

Die Klassendefinition muss ausreichen, damit der Compiler überall dort, wo Sie ein Objekt der Klasse verwendet haben, ein identisches Layout im Speicher erstellt. Zum Beispiel gegeben etwas wie:

class X { 
    int a;
public:
    int b;
};

Der Compiler hat normalerweise a bei Offset 0 und b bei Offset 4. Wenn der Compiler dies als gerecht ansah:

class X { 
public:
    int b;
};

Es würde "denken", dass b auf Offset 0 anstelle von Offset 4 sein sollte. Wenn Code, der diese Definition verwendet, die b zugewiesen ist, würde Code, der die erste Definition verwendet, a get sehen modifiziert und umgekehrt.

Die übliche Methode, um die Auswirkungen von Änderungen an den privaten Teilen der Klasse zu minimieren, wird normalerweise als Pimpl-Redewendung bezeichnet (über die Google sicher viele Informationen geben kann).

17
Jerry Coffin

Es gibt höchstwahrscheinlich mehrere Gründe. Während auf private Mitglieder von den meisten anderen Klassen nicht zugegriffen werden kann, können Freunde-Klassen dennoch auf sie zugreifen. Zumindest in diesem Fall werden sie möglicherweise in der Kopfzeile benötigt, damit die Freundesklasse sehen kann, dass sie vorhanden sind.

Die Neukompilierung abhängiger Dateien hängt möglicherweise von Ihrer Include-Struktur ab. Das Einfügen der .h-Dateien in eine .cpp-Datei anstelle eines anderen Headers kann in einigen Fällen lange Ketten von Neukompilierungen verhindern.

3

Der Hauptgrund dafür ist, dass jeder Code, der eine Klasse verwendet, über private Klassenmitglieder Bescheid wissen muss, um Code zu generieren, der damit umgehen kann.

Betrachten Sie die folgende Klasse:

//foo.h
class foo {
    char private_member[0x100];
public:
    void do_something();
};

welches vom folgenden Code verwendet wird:

#include "foo.h"
void f() {
    foo x;
    x.do_something();
}

Wenn wir diesen Code unter 32-Bit-Linux mit gcc kompilieren und einige Flags zur Vereinfachung der Analyse verwenden, wird die Funktion f mit (mit Kommentaren) kompiliert:

;allocate 256 bytes on the stack for a foo, plus 4 bytes for a foo*
   0:   81 ec 04 01 00 00       sub    esp,0x104
;the trivial constructor for foo is elided here
;compute the address of x
   6:   8d 44 24 04             lea    eax,[esp+0x4]
;pass the foo* to the function being called (the implicit first argument, this)
   a:   89 04 24                mov    DWORD PTR [esp],eax
;call x.do_something()
   d:   e8 fc ff ff ff          call   e <_Z1fv+0xe>
;deallocate the stack space used for this function
  12:   81 c4 04 01 00 00       add    esp,0x104
;return
  18:   c3                      ret    

Hier sind zwei Dinge zu beachten:

  • Der Code für f() muss sizeof(foo) kennen, um den richtigen Speicherplatz dafür zuzuweisen.
  • Der Konstruktor von foo wird nicht aufgerufen. Dies liegt daran, dass der Konstruktor trivial ist, aber es ist unmöglich zu wissen, ob foo einen trivialen Konstruktor hat, ohne seine privaten Klassenmitglieder zu kennen.

Während der Programmierer nicht über die Implementierung einer Klasse Bescheid wissen muss, um sie verwenden zu können, tut dies der Compiler im Wesentlichen. Die C++ - Designer hätten zulassen können, dass private Klassenmitglieder dem Clientcode unbekannt sind, indem sie einige Indirektionsebenen eingeführt haben. Dies hätte jedoch in einigen Fällen schwerwiegende Auswirkungen auf die Leistung. Stattdessen kann der Programmierer entscheiden, diese Indirektion selbst zu implementieren (z. B. über das pImpl-Idiom ), wenn er entscheidet, dass sich der Kompromiss lohnt.

2
Chris

Das zugrunde liegende Problem besteht darin, dass die Header-Datei in C++ Informationen enthalten muss, die der Compiler benötigt, aber auch als Referenz für einen menschlichen Benutzer einer Klasse verwendet wird.

Als menschlicher Benutzer einer Klasse interessieren mich viele Dinge nicht. Private Felder sind eine, Inline-Implementierungen von Funktionen und Methoden sind eine andere. Der Compiler hingegen kümmert sich sehr darum.

Moderne Sprachen wie Swift lösen dies mit etwas mehr CPU-Zeit: Es gibt nur eine permanent gespeicherte Datei, und das ist die Quelldatei. Der Compiler erstellt hinter den Kulissen so etwas wie eine Header-Datei, die enthält Alles, was es braucht, um andere Quelldateien mit der Klasse zu kompilieren. Und die Idee ermöglicht es, dem menschlichen Benutzer zu zeigen, was in C++ die Header-Datei wäre, wobei nur die Dinge enthalten sind, die der Benutzer möchte, das sind keine privaten Methoden, sondern alle Kommentare für Typen, Konstanten, Methoden usw. vorhanden. Genau das, was der menschliche Benutzer möchte. (Der Xcode IDE zeigt optional, wie Methoden aus einer völlig anderen Sprache aufgerufen werden).

0
gnasher729