Warum kann ich im Kopierkonstruktor auf private Variablen zugreifen?

85

Ich habe gelernt, dass ich niemals auf eine private Variable zugreifen kann, nur mit einer get-Funktion in der Klasse. Aber warum kann ich dann im Kopierkonstruktor darauf zugreifen?

Beispiel:

Field::Field(const Field& f)
{
  pFirst = new T[f.capacity()];

  pLast = pFirst + (f.pLast - f.pFirst);
  pEnd  = pFirst + (f.pEnd - f.pFirst);
  std::copy(f.pFirst, f.pLast, pFirst);
}

Meine Erklärung:

private:
  T *pFirst,*pLast,*pEnd;
Dämonen König
quelle
Da der Kopierkonstruktor standardmäßig ein Klassenmitglied ist, sind es auch einige andere.
DumbCoder
+ 53 / -0? Wer hat dafür gestimmt? Wie würden Sie sie sonst kopieren ?!? (Lassen Sie uns die Nicht-Alternativen entlarven: Erstellen Sie für jedes private Mitglied einen öffentlichen Referenz-Getter? Dann sind sie überhaupt nicht privat. Machen Sie für jedes Mitglied einen öffentlichen const&oder By-Value-Getter? Dann sind sie nur "Write-Private", & für Werte verschwenden Ressourcen und scheitern für nicht kopierbare Mitglieder.) Ich bin verblüfft über den Erfolg einer so leeren Frage, die nach der Erstellung von Kopien fragt und dabei völlig ignoriert, was dies bedeutet, und keine Antwort verwendet grundlegende Logik, um sie zu entlarven. Sie erklären trockene Techniken, aber es gibt eine viel einfachere Antwort auf eine Frage, die so blinzelte
underscore_d
8
@underscore_d, "Wie würden Sie sie sonst kopieren?" ist meiner Meinung nach eine sehr seltsame Antwort. Es ist wie die Antwort "Wie funktioniert die Schwerkraft?" mit "wie sonst würden die Dinge fallen!" Das Verwechseln der Kapselung auf Klassenebene mit der Kapselung auf Objektebene ist in der Tat weit verbreitet. Es ist lustig, wie Sie denken, dass es eine dumme Frage ist und dass die Antwort offensichtlich sein sollte. Beachten Sie, dass die Kapselung in Smalltalk (wohl die archetypische OO-Sprache) tatsächlich auf Objektebene funktioniert.
Aioobe
@aioobe Guter Punkt, danke. Mein Kommentar ist etwas extrem - vielleicht war die Kaffeemaschine an diesem Tag kaputt. Ich schätze es, dass Sie darauf hinweisen, warum diese Frage beliebt ist, insbesondere bei Personen, die aus anderen (und vielleicht mehr) OO-Sprachen stammen. Tatsächlich ist es fraglich, ob mein Kommentar "blinzelte", da ich aus der Perspektive von jemandem schrieb, der hauptsächlich in C ++ programmiert. Lieben Sie auch diese Schwerkraftanalogie!
underscore_d

Antworten:

32

IMHO, vorhandene Antworten machen einen schlechten Job und erklären das "Warum" davon - konzentrieren sich zu sehr darauf, zu wiederholen, welches Verhalten gültig ist. "Zugriffsmodifikatoren funktionieren auf Klassenebene und nicht auf Objektebene." - Ja aber warum?

Das übergeordnete Konzept hier ist, dass es die Programmierer sind, die eine Klasse entwerfen, schreiben und pflegen, von denen erwartet wird, dass sie die gewünschte OO-Kapselung verstehen und befugt sind, ihre Implementierung zu koordinieren. Wenn Sie also schreiben class X, codieren Sie nicht nur, wie ein einzelnes X xObjekt von Code mit Zugriff darauf verwendet werden kann, sondern auch wie:

  • abgeleitete Klassen können damit interagieren (durch optional reine virtuelle Funktionen und / oder geschützten Zugriff) und
  • Verschiedene XObjekte arbeiten zusammen , um beabsichtigte Verhaltensweisen bereitzustellen und gleichzeitig die Nachbedingungen und Invarianten Ihres Entwurfs zu berücksichtigen.

Es ist nicht nur der Kopierkonstruktor - eine Vielzahl von Operationen kann zwei oder mehr Instanzen Ihrer Klasse umfassen: Wenn Sie vergleichen, addieren / multiplizieren / dividieren, kopieren, klonen, zuweisen usw., ist dies häufig der Fall Entweder muss es einfach Zugriff auf private und / oder geschützte Daten im anderen Objekt haben oder es soll eine einfachere, schnellere oder allgemein bessere Funktionsimplementierung ermöglichen.

Insbesondere möchten diese Vorgänge möglicherweise den privilegierten Zugriff nutzen, um Folgendes zu tun:

  • (Kopierkonstruktoren) verwenden ein privates Mitglied des Objekts "rhs" (rechte Seite) in einer Initialisiererliste, sodass eine Mitgliedsvariable selbst kopierkonstruiert wird, anstatt standardmäßig konstruiert (wenn auch legal) und dann ebenfalls zugewiesen (erneut) falls legal)
  • Ressourcen gemeinsam nutzen - Dateihandles, Segmente für gemeinsam genutzten Speicher, shared_ptr s zum Referenzieren von Daten usw.
  • Dinge in Besitz nehmen, z auto_ptr<> "verlagert" das Eigentum an dem im Bau befindlichen Objekt
  • Kopieren Sie private "Cache" -, Kalibrierungs- oder Statuselemente, die erforderlich sind, um das neue Objekt in einem optimal verwendbaren Zustand zu erstellen, ohne sie von Grund auf neu generieren zu müssen
  • Kopier- / Zugriffsdiagnose- / Ablaufverfolgungsinformationen, die in dem zu kopierenden Objekt gespeichert sind und auf die sonst nicht über öffentliche APIs zugegriffen werden kann, die jedoch möglicherweise von einem späteren Ausnahmeobjekt oder einer Protokollierung verwendet werden (z. B. Angaben zu Zeitpunkt / Umständen, zu denen die "ursprüngliche" nicht kopierte Instanz erstellt wurde wurde errichtet)
  • Führen Sie eine effizientere Kopie einiger Daten durch: z. B. können Objekte z. B. ein unordered_mapMitglied haben, aber nur öffentlich verfügbar machen begin()und end()iterieren - mit direktem Zugriff auf size()Sie können Sie reserveschneller kopieren. Schlimmer noch, wenn sie nur aussetzen at()und insert()und sonst throw....
  • Kopieren Sie Verweise zurück auf übergeordnete / Koordinations- / Verwaltungsobjekte, die für den Clientcode möglicherweise unbekannt oder schreibgeschützt sind
Tony Delroy
quelle
2
Ich denke, das größte "Warum" ist, dass es ein enormer Laufzeitaufwand wäre, zu überprüfen, ob this == otherSie bei jedem Zugriff darauf zugreifen other.xmüssen, wenn die Zugriffsmodifikatoren auf Objektebene funktionieren.
Aioobe
2
@aioobe Ich denke, deine Antwort sollte viel, viel prominenter sein. Während Tonys Antwort wirklich gut und konzeptionell ist, würde ich wetten, dass Ihre Antwort der eigentliche historische Grund für die Wahl ist, wenn ich ein Wettmann wäre. Es ist nicht nur performanter, sondern auch viel einfacher. Wäre eine gute Frage für Bjarne!
Nir Friedman
Ich habe Ihre Antwort markiert, weil sie den Hintergrund erklärt;)
Dämonisierung
@demonking, ich denke, die Gründe in dieser Antwort decken ab, warum es bequem ist , die privaten Daten für andere Objekte offen zu lassen. Zugriffsmodifikatoren sollen Daten jedoch nicht "offen" genug machen. Sie sollen die Daten eher so schließen , dass sie gekapselt werden können. (Aus praktischen Gründen wäre es sogar noch besser, wenn private Variablen öffentlich wären !) Ich habe meine Antwort mit einem Abschnitt aktualisiert, der meiner Meinung nach das eigentliche Warum besser anspricht .
Aioobe
@aioobe: alte Kommentare, aber trotzdem ... "überprüfe, ob this == otherjedes Mal , wenn du zugreifst other.x" - verfehlt den Punkt - wenn other.xes nur zur Laufzeit akzeptiert wurde, wenn es äquivalent zu ist this.x, würde es überhaupt nicht viel Zeigerschreiben geben other.x; Der Compiler könnte Sie genauso gut zwingen, if (this == other) ...this.x...für alles zu schreiben, was Sie tun würden. Ihre Konzeption "Bequemlichkeit (noch mehr, wenn private Variablen öffentlich waren)" geht ebenfalls daneben - die Art und Weise, wie die Definition des Standards restriktiv genug ist, um eine ordnungsgemäße Kapselung zu ermöglichen , ist jedoch nicht unnötig unpraktisch.
Tony Delroy
108

Die Zugriffsmodifikatoren arbeiten auf Klassenebene und nicht auf Objektebene .

Das heißt, zwei Objekte derselben Klasse können auf private Daten des jeweils anderen zugreifen.

Warum:

Vor allem aus Effizienzgründen. Es wäre ein nicht zu vernachlässigender Laufzeitaufwand, bei this == otherjedem Zugriff zu überprüfen, obother.x , ob die Zugriffsmodifikatoren auf Objektebene funktionieren würden.

Es ist auch semantisch logisch, wenn Sie es in Bezug auf den Umfang betrachten: "Wie viel Teil des Codes muss ich beim Ändern einer privaten Variablen berücksichtigen?" - Sie müssen den Code der gesamten Klasse berücksichtigen, und dies ist orthogonal zu den Objekten, die zur Laufzeit vorhanden sind.

Und es ist unglaublich praktisch, wenn Sie Kopierkonstruktoren und Zuweisungsoperatoren schreiben.

aioobe
quelle
35

Sie können innerhalb der Klasse auf private Mitglieder einer Klasse zugreifen, auch auf solche einer anderen Instanz.

Alexander Rafferty
quelle
10

Um die Antwort zu verstehen, möchte ich Sie an einige Konzepte erinnern.

  1. Unabhängig davon, wie viele Objekte Sie erstellen, befindet sich für diese Klasse nur eine Kopie einer Funktion im Speicher. Dies bedeutet, dass Funktionen nur einmal erstellt werden. Die Variablen sind jedoch für jede Instanz der Klasse separat.
  2. this Der Zeiger wird beim Aufruf an jede Funktion übergeben.

Jetzt ist es wegen der this Zeigers, dass die Funktion in der Lage ist, Variablen dieser bestimmten Instanz zu lokalisieren. egal ob es privat oder öffentlich ist. Innerhalb dieser Funktion kann darauf zugegriffen werden. Wenn wir nun einen Zeiger auf ein anderes Objekt derselben Klasse übergeben. Mit diesem zweiten Zeiger können wir auf private Mitglieder zugreifen.

Hoffe das beantwortet deine Frage.

Ali Zaib
quelle
6

Der Kopierkonstruktor ist eine Mitgliedsfunktion der Klasse und hat als solche Zugriff auf Datenelemente der Klasse, auch auf solche, die als "privat" deklariert sind.

Bojan Komazec
quelle
3
Als jemand, der die Sprache kennt, verstehe ich, was Sie meinen. Wenn ich die Sprache jedoch nicht gekannt hätte, hätte ich gedacht, dass Sie mir die Frage nur wiederholen.
San Jacinto