Forum: PC-Programmierung Heap & Kopierkonstruktor


von peter (Gast)


Lesenswert?

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
      delete m_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

von Kopieren bei initialisierung (Gast)


Lesenswert?

Du brauchst das Ding beim initialisieren einer Instanz mit einer bereits 
vorhandenen Instanz.

von Timm R. (Firma: privatfrickler.de) (treinisch)


Lesenswert?

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

von Timm R. (Firma: privatfrickler.de) (treinisch)


Lesenswert?

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

: Bearbeitet durch User
von je ++ desto Bugs (Gast)


Lesenswert?

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

von Kaj (Gast)


Lesenswert?

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++.

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

Der Zeiger wird auf NULL gesetzt, damit du im weiteren Programmverlauf 
pruefen kannst, ob der Pointer noch Gueltig ist. Stichworte:
- Use-After-Free
- dangling pointer

https://www.webopedia.com/TERM/U/use-after-free.html
1
Use After Free
2
3
Use-After-Free vulnerabilities are a type of memory corruption flaw
4
that can be leveraged by hackers to execute arbitrary code.
5
6
Use After Free specifically refers to the attempt to access memory after
7
it has been freed, which can cause a program to crash or, in the case of
8
a Use-After-Free flaw, can potentially result in the execution of
9
arbitrary code or even enable full remote code execution capabilities.
10
11
12
The Specifics of Use-After-Free Memory Corruption
13
14
According to the Use After Free definition on the Common Weakness
15
Enumeration (CWE) website, a Use After Free scenario can occur when
16
"the memory in question is allocated to another pointer validly at
17
some point after it has been freed. The original pointer to the freed
18
memory is used again and points to somewhere within the new allocation.
19
As the data is changed, it corrupts the validly used memory; this
20
induces undefined behavior in the process."

https://en.wikipedia.org/wiki/Dangling_pointer
1
Dangling pointers and wild pointers in computer programming are pointers 
2
that do not point to a valid object of the appropriate type. These are 
3
special cases of memory safety violations. More generally, dangling 
4
references and wild references are references that do not resolve to a 
5
valid destination, and include such phenomena as link rot on the internet.
6
7
Dangling pointers arise during object destruction, when an object that has 
8
an incoming reference is deleted or deallocated, without modifying the 
9
value of the pointer, so that the pointer still points to the memory 
10
location of the deallocated memory. The system may reallocate the 
11
previously freed memory, and if the program then dereferences the (now) 
12
dangling pointer, unpredictable behavior may result, as the memory may now 
13
contain completely different data. If the program writes to memory 
14
referenced by a dangling pointer, a silent corruption of unrelated data may
15
 result, leading to subtle bugs that can be extremely difficult to find. 
16
If the memory has been reallocated to another process, then attempting to 
17
dereference the dangling pointer can cause segmentation faults (UNIX, 
18
Linux) or general protection faults (Windows). If the program has 
19
sufficient privileges to allow it to overwrite the bookkeeping data used 
20
by the kernel's memory allocator, the corruption can cause system 
21
instabilities.

von Rolf M. (rmagnus)


Lesenswert?

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.

von M.K. B. (mkbit)


Lesenswert?

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.

von Kai S. (zigzeg)


Lesenswert?

> 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

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

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.

von Purzel H. (hacky)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.