Ich weiß, dass der C ++ - Compiler einen Kopierkonstruktor für eine Klasse erstellt. In welchem Fall müssen wir einen benutzerdefinierten Kopierkonstruktor schreiben? Können Sie einige Beispiele nennen?
c++
copy-constructor
Penguru
quelle
quelle
Antworten:
Der vom Compiler generierte Kopierkonstruktor kopiert die Mitglieder. Manchmal reicht das nicht aus. Beispielsweise:
In diesem Fall
stored
wird der Puffer durch das kopieren des Mitglieds durch Mitglieder nicht dupliziert (nur der Zeiger wird kopiert), sodass die erste zu zerstörende Kopie, die den Puffer gemeinsam nutzt,delete[]
erfolgreich aufgerufen wird und die zweite auf undefiniertes Verhalten stößt. Sie benötigen einen Kopierkonstruktor für tiefes Kopieren (und auch einen Zuweisungsoperator).quelle
delete stored[];
und ich glaube, es sollte seindelete [] stored;
std::string
. Die allgemeine Idee ist, dass nur Dienstprogrammklassen, die Ressourcen verwalten, die Big Three überladen müssen und dass alle anderen Klassen nur diese Dienstprogrammklassen verwenden sollten, sodass keine der Big Three definiert werden muss.Ich bin ein bisschen verärgert, dass die Regel der
Rule of Five
nicht zitiert wurde.Diese Regel ist sehr einfach:
Es gibt jedoch eine allgemeinere Richtlinie, die Sie befolgen sollten, die sich aus der Notwendigkeit ergibt, ausnahmesicheren Code zu schreiben:
Hier ist
@sharptooth
der Code immer noch (meistens) in Ordnung, aber wenn er seiner Klasse ein zweites Attribut hinzufügen würde, wäre dies nicht der Fall. Betrachten Sie die folgende Klasse:Was passiert bei
new Bar
Würfen? Wie löscht man das Objekt, auf das gezeigt wirdmFoo
? Es gibt Lösungen (Funktionsebene try / catch ...), sie skalieren einfach nicht.Der richtige Weg, um mit der Situation umzugehen, besteht darin, anstelle von Rohzeigern geeignete Klassen zu verwenden.
Mit der gleichen Konstruktorimplementierung (oder tatsächlich mit
make_unique
) habe ich jetzt die Ausnahmesicherheit kostenlos !!! Ist es nicht aufregend? Und das Beste ist, ich muss mir keine Sorgen mehr um einen richtigen Zerstörer machen! Ich muss meine eigenen schreibenCopy Constructor
undAssignment Operator
zwar, weilunique_ptr
diese Operationen nicht definiert sind ... aber das spielt hier keine Rolle;)Und deshalb hat
sharptooth
die Klasse noch einmal besucht:Ich weiß nichts über dich, aber ich finde meine einfacher;)
quelle
Ich kann mich an meine Praxis erinnern und an die folgenden Fälle denken, in denen es darum geht, den Kopierkonstruktor explizit zu deklarieren / zu definieren. Ich habe die Fälle in zwei Kategorien eingeteilt
Korrektheit / Semantik
Ich füge in diesen Abschnitt die Fälle ein, in denen das Deklarieren / Definieren des Kopierkonstruktors für den korrekten Betrieb der Programme unter Verwendung dieses Typs erforderlich ist.
Nachdem Sie diesen Abschnitt gelesen haben, lernen Sie einige Fallstricke kennen, die es dem Compiler ermöglichen, den Kopierkonstruktor selbst zu generieren. Daher ist , wie seand in seiner bemerkte Antwort , ist es immer sicher Kopierbarkeit für eine neue Klasse auszuschalten und absichtlich es später ermöglichen , wenn es wirklich erforderlich ist .
So machen Sie eine Klasse in C ++ 03 nicht kopierbar
Deklarieren Sie einen privaten Kopierkonstruktor und stellen Sie keine Implementierung dafür bereit (sodass der Build in der Verknüpfungsphase fehlschlägt, selbst wenn die Objekte dieses Typs im eigenen Bereich der Klasse oder von ihren Freunden kopiert werden).
So machen Sie eine Klasse in C ++ 11 oder neuer nicht kopierbar
Deklarieren Sie den Kopierkonstruktor mit
=delete
am Ende.Shallow vs Deep Copy
Dies ist der am besten verstandene Fall und tatsächlich der einzige, der in den anderen Antworten erwähnt wird. Shaprtooth hat es ziemlich gut abgedeckt . Ich möchte nur hinzufügen, dass das gründliche Kopieren von Ressourcen, die ausschließlich dem Objekt gehören sollten, auf alle Arten von Ressourcen angewendet werden kann, von denen dynamisch zugewiesener Speicher nur eine Art ist. Bei Bedarf kann auch ein gründliches Kopieren eines Objekts erforderlich sein
Selbstregistrierende Objekte
Stellen Sie sich eine Klasse vor, in der alle Objekte - egal wie sie konstruiert wurden - irgendwie registriert sein MÜSSEN. Einige Beispiele:
Das einfachste Beispiel: Beibehalten der Gesamtzahl der aktuell vorhandenen Objekte. Bei der Objektregistrierung wird lediglich der statische Zähler erhöht.
Ein komplexeres Beispiel ist eine Singleton-Registrierung, in der Verweise auf alle vorhandenen Objekte dieses Typs gespeichert werden (damit Benachrichtigungen an alle gesendet werden können).
Referenzgezählte Smart-Zeiger können in dieser Kategorie nur als Sonderfall betrachtet werden: Der neue Zeiger "registriert" sich selbst bei der gemeinsam genutzten Ressource und nicht in einer globalen Registrierung.
Eine solche Selbstregistrierungsoperation muss von JEDEM Konstruktor des Typs ausgeführt werden, und der Kopierkonstruktor ist keine Ausnahme.
Objekte mit internen Querverweisen
Einige Objekte haben möglicherweise eine nicht triviale interne Struktur mit direkten Querverweisen zwischen ihren verschiedenen Unterobjekten (tatsächlich reicht nur ein solcher interner Querverweis aus, um diesen Fall auszulösen). Der Compiler bereitgestellten Copykonstruktor die internen brechen intra-Objekt Verbände, um sie zu konvertieren zwischen Objekten Verbände.
Ein Beispiel:
Es dürfen nur Objekte kopiert werden, die bestimmte Kriterien erfüllen
Es kann Klassen geben, in denen Objekte in einem bestimmten Zustand (z. B. Standard-Konstruktionszustand) sicher kopiert und ansonsten nicht sicher kopiert werden können. Wenn wir das Kopieren von sicher zu kopierenden Objekten zulassen möchten, benötigen wir - wenn wir defensiv programmieren - eine Laufzeitprüfung im benutzerdefinierten Kopierkonstruktor.
Nicht kopierbare Unterobjekte
Manchmal aggregiert eine Klasse, die kopierbar sein sollte, nicht kopierbare Unterobjekte. Normalerweise geschieht dies für Objekte mit nicht beobachtbarem Zustand (dieser Fall wird im Abschnitt "Optimierung" weiter unten ausführlicher erläutert). Der Compiler hilft lediglich, diesen Fall zu erkennen.
Quasi kopierbare Unterobjekte
Eine Klasse, die kopierbar sein sollte, kann ein Unterobjekt eines quasi kopierbaren Typs aggregieren. Ein quasi kopierbarer Typ stellt keinen Kopierkonstruktor im engeren Sinne bereit, verfügt jedoch über einen anderen Konstruktor, mit dem eine konzeptionelle Kopie des Objekts erstellt werden kann. Der Grund dafür, einen Typ quasi kopierbar zu machen, liegt darin, dass keine vollständige Übereinstimmung über die Kopiersemantik des Typs besteht.
Die endgültige Entscheidung bleibt dann den Benutzern dieses Typs überlassen. Beim Kopieren von Objekten müssen sie explizit (durch zusätzliche Argumente) die beabsichtigte Kopiermethode angeben.
Im Falle eines nicht defensiven Programmieransatzes ist es auch möglich, dass sowohl ein regulärer Kopierkonstruktor als auch ein Quasi-Kopierkonstruktor vorhanden sind. Dies kann gerechtfertigt sein, wenn in den allermeisten Fällen eine einzige Kopiermethode angewendet werden sollte, während in seltenen, aber gut verstandenen Situationen alternative Kopiermethoden verwendet werden sollten. Dann beschwert sich der Compiler nicht darüber, dass er den Kopierkonstruktor nicht implizit definieren kann. Es liegt in der alleinigen Verantwortung des Benutzers, sich zu merken und zu prüfen, ob ein Unterobjekt dieses Typs über einen Quasi-Kopierkonstruktor kopiert werden soll.
Kopieren Sie keinen Status, der stark mit der Identität des Objekts verbunden ist
In seltenen Fällen kann eine Teilmenge des beobachtbaren Zustands des Objekts einen untrennbaren Teil der Identität des Objekts darstellen (oder als solcher betrachtet werden) und sollte nicht auf andere Objekte übertragbar sein (obwohl dies etwas kontrovers sein kann).
Beispiele:
Die UID des Objekts (aber diese gehört auch zum Fall "Selbstregistrierung" von oben, da die ID in einem Akt der Selbstregistrierung erhalten werden muss).
Verlauf des Objekts (z. B. des Rückgängig / Wiederherstellen-Stapels) für den Fall, dass das neue Objekt nicht den Verlauf des Quellobjekts erben darf, sondern mit einem einzelnen Verlaufselement " Kopiert um <ZEIT> von <OTHER_OBJECT_ID> " beginnt .
In solchen Fällen muss der Kopierkonstruktor das Kopieren der entsprechenden Unterobjekte überspringen.
Erzwingen der korrekten Signatur des Kopierkonstruktors
Die Signatur des vom Compiler bereitgestellten Kopierkonstruktors hängt davon ab, welche Kopierkonstruktoren für die Unterobjekte verfügbar sind. Wenn mindestens ein Unterobjekt keinen echten Kopierkonstruktor hat (das Quellobjekt als konstante Referenz verwendet), sondern einen mutierenden Kopierkonstruktor (das Quellobjekt als nicht konstante Referenz verwendet), hat der Compiler keine Wahl aber implizit einen mutierenden Kopierkonstruktor deklarieren und dann definieren.
Was ist nun, wenn der "mutierende" Kopierkonstruktor vom Typ des Unterobjekts das Quellobjekt nicht tatsächlich mutiert (und einfach von einem Programmierer geschrieben wurde, der das
const
Schlüsselwort nicht kennt )? Wenn wir diesen Code nicht durch Hinzufügen des fehlenden Codes reparieren könnenconst
, besteht die andere Möglichkeit darin, unseren eigenen benutzerdefinierten Kopierkonstruktor mit einer korrekten Signatur zu deklarieren und die Sünde zu begehen, sich an a zu wendenconst_cast
.Copy-on-Write (COW)
Ein COW-Container, der direkte Verweise auf seine internen Daten preisgegeben hat, MUSS zum Zeitpunkt der Erstellung tief kopiert werden, da er sich sonst möglicherweise als Referenzzählhandle verhält.
Optimierung
In den folgenden Fällen möchten / müssen Sie möglicherweise Ihren eigenen Kopierkonstruktor aus Optimierungsgründen definieren:
Strukturoptimierung beim Kopieren
Stellen Sie sich einen Container vor, der Elemententfernungsvorgänge unterstützt, dies jedoch möglicherweise, indem Sie das entfernte Element einfach als gelöscht markieren und seinen Steckplatz später wiederverwenden. Wenn eine Kopie eines solchen Containers erstellt wird, kann es sinnvoll sein, die überlebenden Daten zu komprimieren, anstatt die "gelöschten" Slots unverändert beizubehalten.
Überspringen Sie das Kopieren des nicht beobachtbaren Zustands
Ein Objekt kann Daten enthalten, die nicht Teil seines beobachtbaren Zustands sind. In der Regel handelt es sich dabei um zwischengespeicherte / gespeicherte Daten, die während der Lebensdauer des Objekts gesammelt wurden, um bestimmte langsame Abfragevorgänge zu beschleunigen, die vom Objekt ausgeführt werden. Das Kopieren dieser Daten kann sicher übersprungen werden, da sie neu berechnet werden, wenn (und wenn!) Die entsprechenden Vorgänge ausgeführt werden. Das Kopieren dieser Daten kann ungerechtfertigt sein, da es schnell ungültig werden kann, wenn der beobachtbare Zustand des Objekts (von dem die zwischengespeicherten Daten abgeleitet werden) durch Mutationsoperationen geändert wird (und wenn wir das Objekt nicht ändern wollen, warum erstellen wir eine Tiefe dann kopieren?)
Diese Optimierung ist nur gerechtfertigt, wenn die Hilfsdaten im Vergleich zu den Daten, die den beobachtbaren Zustand darstellen, groß sind.
Deaktivieren Sie das implizite Kopieren
Mit C ++ kann das implizite Kopieren deaktiviert werden, indem der Kopierkonstruktor deklariert wird
explicit
. Dann können Objekte dieser Klasse nicht an Funktionen übergeben und / oder von Funktionen nach Wert zurückgegeben werden. Dieser Trick kann für einen Typ verwendet werden, der leicht zu sein scheint, aber in der Tat sehr teuer zu kopieren ist (obwohl es eine bessere Wahl sein könnte, ihn quasi kopierbar zu machen).TODOs
quelle
Wenn Sie eine Klasse haben, die dynamisch Inhalt zugewiesen hat. Zum Beispiel speichern Sie den Titel eines Buches als Zeichen * und setzen den Titel auf neu. Die Kopie funktioniert nicht.
Sie müssten einen Kopierkonstruktor schreiben, der dies tut
title = new char[length+1]
und dannstrcpy(title, titleIn)
. Der Kopierkonstruktor würde nur eine "flache" Kopie erstellen.quelle
Der Kopierkonstruktor wird aufgerufen, wenn ein Objekt entweder als Wert übergeben, als Wert zurückgegeben oder explizit kopiert wird. Wenn kein Kopierkonstruktor vorhanden ist, erstellt c ++ einen Standardkopierkonstruktor, der eine flache Kopie erstellt. Wenn das Objekt keine Zeiger auf dynamisch zugewiesenen Speicher hat, reicht eine flache Kopie aus.
quelle
Es ist oft eine gute Idee, copy ctor und operator = zu deaktivieren, es sei denn, die Klasse benötigt es speziell. Dies kann Ineffizienzen wie das Übergeben eines Args als Wert verhindern, wenn eine Referenz beabsichtigt ist. Auch die vom Compiler generierten Methoden können ungültig sein.
quelle
Betrachten wir das folgende Code-Snippet:
b2.ShowData();
Gibt eine Junk-Ausgabe aus, da ein benutzerdefinierter Kopierkonstruktor erstellt wurde, für den kein Code zum expliziten Kopieren von Daten geschrieben wurde. Der Compiler erstellt also nicht dasselbe.Ich dachte nur daran, dieses Wissen mit Ihnen allen zu teilen, obwohl die meisten von Ihnen es bereits wissen.
Prost ... Viel Spaß beim Codieren !!!
quelle