Hallo,
Habe 2 Fragen an dieses Forum.
Gibt es eine Faustregel oder Regeln, die besagen, wann ich einen eigenen
Kopierkonstruktor schreiben muss?
Soweit ich das verstehe, muss ich immer einen Copy Constructor
erstellen, wenn meine Klasse einen Zeiger auf Heapspeicher enthält und
dieser Zeiger beim kopieren der Klasse wie alle anderen Daten eben auch
vervielfältigt also kopiert werden.
Für mein Verständnis hat der Copy Construktor also immer was mit Zeiger
auf Heapspeicher zu tun.
Gibt es sonst noch Gründe, wann ich einen Copy Constructor erstellen
muss?
Zweite Frage geht Richtung freigeben von Heapspeicher.
.....Eine int Variable wurde auf dem Heap angelegt und die
Speicheradresse in "m_p_Heap" zurückgegeben.
Oft gesehene Freigabe:
1
if(m_p_Heap!=NULL)
2
{
3
deletem_p_Heap;
4
m_p_Heap=NULL
5
}
Das auf NULL setzen verstehe ich. Zeiger die auf NULL zeigen, kann man
so oft deleten wie man mag. Aber warum wird dann abgefragt
m_p_Heap != NULL
Kopieren bei initialisierung schrieb:> Du brauchst das Ding beim initialisieren einer Instanz mit einer bereits> vorhandenen Instanz.
der TO fragte nicht, wofür er einen Copy Constructor braucht, sondern
wann er einen eigenen schreiben muss ...
Viele liebe Grüße
Timm
Hallo Peter,
zu Frage 1:
Meines Erachtens ist das im Großen und Ganzen so.
Zusätzlich: Wenn Deine Klasse mindestens ein Member hat, das nicht
kopiert werden kann (kein Copyconstruktur erreichbar), die Basisklasse
nicht kopiert werden kann, die Basisklasse nicht destruiert werden kann,
Deine Klasse einen non-default move-constructor hat oder (exotischer?)
ein && Member hat, dann ist der default copy-Konstruktor deleted. Der
Compiler wird dich dann darauf hinweisen, dass Deine Klasse einen
handgemachten Copy Konstruktor braucht, wenn Du versuchst ihn zu
benutzen :-)
Der wichtigste Fall dürfte sein, wenn Du einen Pointer aus der Frage
(vermutlich) korrekt als std::unique_ptr<> auslegst: Der default copy
constructor ist dann deleted und wenn Du versuchst ihn zu benutzen
erklärt der Kompiler Dir das.
Zu Frage 2:
Der wesentliche Grund dürfte sein, dass es so viel einfacher ist einen
Breakpoint / Watchpoint zu setzen, ansonsten ist das einfach Unsinn.
vlg
Timm
Timm R. schrieb:> Wenn Deine Klasse mindestens ein Member hat, das nicht> kopiert werden kann (kein Copyconstruktur erreichbar), die Basisklasse> nicht kopiert werden kann, die Basisklasse nicht destruiert werden kann,> Deine Klasse einen non-default move-constructor hat oder (exotischer?)> ein && Member hat, dann ist der default copy-Konstruktor deleted. Der> Compiler wird dich dann darauf hinweisen, dass Deine Klasse einen> handgemachten Copy Konstruktor braucht, wenn Du versuchst ihn zu> benutzen :-)
Diesen Text sollte man als allg. Warnung vor C++ jedem der sich das
freiwillig antun will als Musterexemplar vorher einmal an die Tafel
pinseln.
Kauderwelsch vom Feinsten! Finger weg, von jeder Schwarte (Buch), die
den verwirrten, geknechteten, armen Leser mit solchen "Erklärungen"
allein zurück lässt.
Kein Wunder, wenn da C++ Coder mangels Verständnis ständig neue Bugs
nach immer gleichem Muster ungewollt in ihr Endprodukt einpflegen:
A use-after-free vulnerability
A heap buffer overflow
https://www.mozilla.org/en-US/security/known-vulnerabilities/firefox-esr/#firefoxesr60.2
peter schrieb:> Soweit ich das verstehe, muss ich immer einen Copy Constructor> erstellen, wenn meine Klasse einen Zeiger auf Heapspeicher enthält und> dieser Zeiger beim kopieren der Klasse wie alle anderen Daten eben auch> vervielfältigt also kopiert werden.
Allgemein, wenn man in der Klasse irgendwelche Ressourcen verwendet, die
du beim Kopieren speziell behandeln musst oder im Destruktor explizit
wieder freigeben. Ob das ein Zeiger auf dynamischen Speicher ist oder
ein Fenter-Handle oder sonstwas ist.
> Für mein Verständnis hat der Copy Construktor also immer was mit Zeiger> auf Heapspeicher zu tun.
Er hat was mit Ressourcen-Handling zu tun.
Kaj schrieb:> peter schrieb:>> Das auf NULL setzen verstehe ich. Zeiger die auf NULL zeigen, kann man>> so oft deleten wie man mag.> Nein, kann man nicht. Das ist undefined behavior. Sowohl in C als auch> in C++.
Ist es nicht.
> https://stackoverflow.com/questions/21057393/what-does-double-free-mean/21057524#21057524> https://stackoverflow.com/questions/9169774/what-happens-in-a-double-delete/9169802#9169802
In keinem dieser Beispiele wird der Zeiger vor dem zweiten delete auf
NULL gesetzt. Gerade deshalb ist es undefined behavior.
peter schrieb:> Das auf NULL setzen verstehe ich. Zeiger die auf NULL zeigen, kann man> so oft deleten wie man mag. Aber warum wird dann abgefragt> m_p_Heap != NULL
Laut dem C++ Standard ist delete NULL erlaubt und die Heapverwaltung tut
in diesem Fall nichts. Ich hatte aber schon Heapimplementierungen für
den Mikrocontroller, die sich daran nicht gehalten haben und dann
abgestürzt sind. In meinem Fall habe ich dann aber den Heap gefixt bevor
ich überall die Checks einbaue.
Rolf M. schrieb:> Allgemein, wenn man in der Klasse irgendwelche Ressourcen verwendet, die> du beim Kopieren speziell behandeln musst oder im Destruktor explizit> wieder freigeben. Ob das ein Zeiger auf dynamischen Speicher ist oder> ein Fenter-Handle oder sonstwas ist.
Ab C++11 würde ich den Copyconstructor in der Deklaration entsprechend
markieren:
copyc'tor = delete // Wenn nicht sinnvoll oder nicht möglich.
copyc'tor = default // Wenn mir die Defaultimplementierung ausreicht.
Selbst implementieren. // Wenn Default nicht ausreicht.
Mit dem default bekomme ich dann auch eine Warnung wenn das nicht geht.
Außerdem ist dann ersichtlich, dass man den Copyconstructor auch
wirklich haben wollte.
> peter schrieb:>> Allgemein, wenn man in der Klasse irgendwelche Ressourcen verwendet, die> du beim Kopieren speziell behandeln musst oder im Destruktor explizit> wieder freigeben. Ob das ein Zeiger auf dynamischen Speicher ist oder> ein Fenter-Handle oder sonstwas ist.
Richtig - typisches Beispiel: Eine Klasse die eine Datei repräsentiert.
Der Konstruktor öffnet die Datei, der Destruktor schliesst sie. Die
Klasse enthält als Member Variable ein Filehandle (z.B. FILE*).
Wenn ich jetzt eine default Kopie mache, gibt es zwei Instanzen mit
demselben Handle. Wenn für eine Instanz der Destruktor aufgerufen wird,
wird die Datei geschlossen. Die andere Instanz arbeitet nun mit einem
ungültigen Handle.
Frage ist nun: Wie implementiert man das Kopieren (egal ob nun
Zuweisung, oder Konstruktor), wenn das Handle (wie FILE*) ein kopieren
nicht vorsieht?
Ab C++11 kann man nun elegant das Verschieben (std::move) einer Instanz
erlauben, aber das Kopieren verbieten => Problem gelöst.
ZigZeg
Kai S. schrieb:> Wie implementiert man das Kopieren (egal ob nun Zuweisung, oder> Konstruktor), wenn das Handle (wie FILE*) ein kopieren nicht vorsieht?
Man könnte ein zweites Handle aufmachen, muss dann aber mit
entsprechenden Sharing-Attributen hantieren.
Allerdings ist eine Datei ein Objekt, das nur einmal existiert; ein eine
Datei repräsentierendes Objekt sollte das also entsprechend
berücksichtigen.
Wird in Instanz A des Objekts in die Datei geschrieben, betrifft das
natürlich auch Instanz B des Objekts.
Mir scheint hier ein "reference counting" sinnvoll zu sein, die Datei
wird erst beim letzten Destruktoraufruf tatsächlich geschlossen.
Rufus Τ. F. schrieb:> Allerdings ist eine Datei ein Objekt, das nur einmal existiert; ein eine> Datei repräsentierendes Objekt sollte das also entsprechend> berücksichtigen.> Mir scheint hier ein "reference counting" sinnvoll zu sein, die Datei> wird erst beim letzten Destruktoraufruf tatsächlich geschlossen.
Das hätte aber das Problem, dass die Objekte sich alle einen gemeinsamen
Positions-Index innerhalb der Datei teilen. Wenn also im einen Objekt zu
einer bestimmten Position gesprungen wird, greift das andere plötzlich
auch darauf zu.
Generell sehe ich nicht viel Sinn darin, ein Datei-Objekt zu kopieren,
deshalb sollte man das einfach ganz verbieten.
Rolf M. schrieb:> Wenn also im einen Objekt zu einer bestimmten Position gesprungen wird,> greift das andere plötzlich auch darauf zu.
Sofern nicht das Verwalten der Position separat gehandhabt wird.
Das ist auch nicht zwingend sinnvoll, denn wenn an eine bestimmte
Position geschrieben wird, dann ist das völlig unabhängig von der Art
der Dateiverwaltung in der Datei und damit auch in allen "Dateiobjekten"
gleichermaßen drin.
> Generell sehe ich nicht viel Sinn darin, ein Datei-Objekt zu kopieren,
Das ist der entscheidende Punkt. Bei der objektorientierten
Programmierung kommt es halt nicht nur darauf an, zu erkennen, was sich
sinnvoll in einem Objekt unterbringen lässt, sondern auch, wie dieses
Objekt mit der realen Umwelt (hier: dem Dateisystem) interagiert.
Und davon ausgehend sind dann Überlegungen angebracht, ob es mehrere
Instanzen geben kann/darf/muss, ob man "singleton-Pattern" o.ä. anwenden
will, oder ob man das grundlegende Programmdesign auch so handhaben
kann, daß ein zentrales Dateiobjekt für alle wo auch immer verstreuten
anderen Objekte, die etwas von der betreffenden Datei wissen wollen,
gewissermaßen die Dateizugriffe als "Dienstleistung" zur Verfügung
stellt.
Vereinfach laesst sich die Loesung beschreiben als :
Solange man sich nicht vorstellen kann wozu man einen Copy Construktor,
resp einen copy(self), braucht, braucht man keinen.