Copy-Konstruktor (Nutzen? kopierte Objekte? tiefe kopie?)
-
hey, tue mich schwer mit dem begriff kopierkonstruktor bzw mit seinem sinn.
ausbeute aus google-recherchen etc. zu dem thema:
1)der Copy-Konstruktor ist (nur) notwendig wenn die Klasse Zeiger enthält bzw Verweise auf fremden Speicher hat.
2)die Aufgabe des Copy-Konstruktor ist die eine Kopie eines Objektes zu erstellen und ist dazu da dafür zu sorgen, dass der Zeiger von dem Orginal-Objekt und der Zeiger vom Kopie-Objekt nicht auf die selbe Speicherzelle verweist.
3)Bei einem kopierten Objekt wird ein Objekt(kopiertes Objekt) mit den Parameter/Werte von einem anderem Objekt (Orginal-Objekt) eins-zu-eins initialisiert.
Meine Fragen:
1)Was ist der besonderen Nutzen bzw. wozu bieten sich kopierte Objekte an?
Bei welchen Art von Programmier-aufgaben sind solche Objekte besonders gut
einzusetzen?2)Wie mache ich aus einer einfachen flachen Kopie eine tiefe Kopie?
Bsp meines copy-konstruktors (klasse)#include <iostream> #include <string> using namespace std; class klasse { private: int *zeiger, weiteres_attribut; public: klasse() //"normaler" Konstruktor { zeiger = new int; *zeiger = 2; } klasse(int a){ *zeiger = a; } //Parameter-Konstruktor ~klasse() //Destruktor <-- extra Destruktor für Copy-Konstruktor notwendig?? { delete zeiger; zeiger = 0; } int getzeiger() { return *zeiger; } const int get_weiteresattribut(){ return weiteres_attribut; } klasse(const klasse& objekt) // Kopierkonstruktor { zeiger = new int; *zeiger = objekt.getzeiger(); weiteres_attribut = objekt.weiteres_attribut; } }
falsches Verständnis vom copy-konstruktor (programm oder ausbeuten)?
Beispiele und Antworten bitte besonders einfach und selbst für null-könner verständlich formulieren.
danke im voraus.
grüße
-
Hi,
du musst die Aussage 1) so lesen:
Wann ist es notwendig selber einen Copy-Ctor anzugeben.Genutzt wird der Copy-Ctor ständig, nur wird er idR vom Kompiler erstellt.
string a = "Hallo"; string b = a; // Copy-Ctor!
Dein Beispiel ist doch schon gut:
Lässt du den Kompiler den Copy-Ctor erzeugen, dann würde er einfach Elementweise kopieren.Klasse a; Klasse b = a; //Hat Klasse jetzt keinen selbst erstellten Copy-Ctor, dann zeigen a und b beide auf den gleichen Speicher *b.zeiger = 99; cout << *a.zeiger; //Output 99
2)Wie mache ich aus einer einfachen flachen Kopie eine tiefe Kopie?
Wie du es gemacht hast.
~klasse() //Destruktor <-- extra Destruktor für Copy-Konstruktor notwendig??
Ja, aber nicht (nur) wegen dem Copy-Ctor, sondern wergen dem new generell.
Wer soll das sonst löschen.
Zuweisungsoperaztor fehlt auch. Google: Regel der grossen 3.
-
class klasse { private: int* zeiger; int weiteres_attribut; public: klasse(int a = 2) : zeiger(new int(a)), weiteres_attribut(0) {} klasse(const klasse& rhs) : zeiger(new int(*rhs.zeiger)), weiteres_attribut(rhs.weiteres_attribut) {} ~klasse() { delete zeiger; } int getZeiger() const { return *zeiger; } int getWeiteresAttribut() const { return weiteres_attribut; } };
Ich hab den Code mal leicht abgeändert.
- Ein Konstruktor reicht durch den Defaultparameter, damit kannst aber musst nicht einen Parameter angeben.
- Initialisierungslisten eingeführt, ansonsten wird bei der Konstruktion eines Objekts der Speicher bereitgestellt, danach mit irgendwas belegt und dann im Konstruktorrumpf werden erst sinnvolle Werte da rein geschrieben.
- Die Methoden const gemacht, da sie das Objekt selbst nicht ändern.
- Das zeiger = 0 im Destruktor ist unnötig, da du nach dem Destruktor nicht auf den Zeiger zugreifen kannst (er ist ja gelöscht mit seinem Objekt) und es zeugt er von schlechtem Design bzw. ungeklaren Besitzverhältnissen.Zum Kopierkonstruktor selbst: Er muss nicht unbedingt Depp-Copies machen, wenn du das nicht willst oder brauchst. Eine gängige Praxis ist sogar, dass man wirklich nur den Zeiger kopiert (sprich 2 Objekte zeigen auf denselben Speicehr) und erst dann eine Deep-Copy durchführt, wenn das "bezeigte" Objekt/Speicher verändert wird. Nennt sich Copy-On-Write und wird gerne mit dem std::shared_ptr umgesetzt, aber was das weiter angeht muss ich passen, hab ich noch nie gemacht
-
Jockelx schrieb:
Lässt du den Kompiler den Copy-Ctor erzeugen, dann würde er einfach Elementweise kopieren.
Meinst du mit elementenweise kopieren, das erstellen einer flachen kopie, also dass nur die adresse vom attribut kopiert wird?
Jockelx schrieb:
Wie du es gemacht hast.
Also entsteht eine flache kopie (und Nicht eine tiefe kopie) nur wenn man keinen copy-konstruktor hat?
bzw was macht denn insbesondere die "tiefe einer kopie" aus?Jockelx schrieb:
Ja, aber nicht (nur) wegen dem Copy-Ctor, sondern wergen dem new generell.
Wer soll das sonst löschen.ich meinte ob es neben dem normalen destruktor dann sozusagen einen "copy-destruktor" (als gegenstück zum COPY-konstruktor) geben muss?!
oder ob der "normale" destruktor dann neben dem speicher der mit dem "normalen" konstruktor angefordert wurde, Auch den speicher freigibt der über den copy-konstruktor angefordert wurde?!
-
@Skym0sh0:
irgendwie erscheint eine "flache kopie" im netz und in büchern sehr fatal, dass es zu fehlern (nicht nur bei der ausgabe) kommen kann:/
-
Dangling schrieb:
@Skym0sh0:
irgendwie erscheint eine "flache kopie" im netz und in büchern sehr fatal, dass es zu fehlern (nicht nur bei der ausgabe) kommen kann:/Ja kann auch. Was ich sagen wollte ist, dass es drauf ankommt was du vorhast und was du bezwecken willst.
Im Allgemeinen hat dein Research Recht damit, dass du immer tiefe Kopien erstellen soll. Sowas wie COW sind halt Optimierungen.
Zum Thema Compiler generierter Kopierkonstruktor:
Wenn du eine Reihe Attribute hast in deiner Klasse (und keinen Copy-Ctor) und du willst dein Objekt kopieren, dann erstellt der Compiler automatisch einen (genau wie Destruktor und Zuweisungsoperator). Alternativ kannst du mit C++11 auch das erstellen eines Default Copy-Ctors erzwingen:class Widget { public: Widget(Widget const& w) = default; };
In dem Fall, dass der Compiler dir den Copy-Ctor erstellt, macht (bzw. versucht, denn Objekte können ja auch keinen Copy-Ctor haben) er folgendes: Er versucht jedes Attribut mit dem Attribut des Übergebenen Objekts zu kopierkonstruieren, sprich du hast hier eine Kaskade oder Rekursion über Kopierkonstruktoren.
Das Problem ist aber dieses hier:
int val = 5; // irgendein Speicherblock... int * p1 = &val; // Zeiger auf diesen Speicherblock int * p2(p1) // der Kopierkonstruierte zweite Zeiger zeigt auch auf den Speicherblock *p1 = 6; // -> val = 6 *p2 = 4; // -> val = 4
Änderst du dann später eins von deinen beiden Objekten, ändert sich auf magische Weise auch das andere. --> doof
Fieser ist es aber, wenn du den Speicher auch noch löschst:
int * p = new int(5); // irgendein Speicher, der irgendwann auch gelöscht werden muss int * p2(p); *p = 6; // -> *p = 6 *p2 = 4; // -> *p = 4 delete p; // -> ok *p2 = 2; // knallt weil es den Speicher nicht mehr gibt delete p2; // dito...
Ist es was klarer geworden?
Edit: den Zeiger p2 im letzten Codebeispiel nennt man Dangling Pointer. Wenn du p nicht deletest (und auch p2 nicht), hast du keine offensichtlichen Fehler, denn der Speicher ist ja noch da und gültig. Du gibst ihn nämlich nie mehr frei, das heisst dann Ressource/Memory Leak.
Und dass du p nicht löscht, aber dann p2, das ist halt eine Sache, die schwer im Kopf zu halten ist in großen Programmen. (Stichwort: RAII ;))
-
Skym0sh0 schrieb:
In dem Fall, dass der Compiler dir den Copy-Ctor erstellt, macht (bzw. versucht, denn Objekte können ja auch keinen Copy-Ctor haben) er folgendes: Er versucht jedes Attribut mit dem Attribut des Übergebenen Objekts zu kopierkonstruieren, sprich du hast hier eine Kaskade oder Rekursion über Kopierkonstruktoren.
Das Problem ist aber dieses hier:
Änderst du dann später eins von deinen beiden Objekten, ändert sich auf magische Weise auch das andere. --> doof
Fieser ist es aber, wenn du den Speicher auch noch löschst
Also erstellen eigentlich alle vom Compiler automatisch erstellte konstruktoren
nur flache kopien und wenn man selbst einen copy-konstruktor in die klasse einbaut, entstehen tiefe kopien (sofern man ein objekt kopiert)?Geht man eigentlich mit einem kopierten objekt genauso um wie mit einem "normalem" objekt? abgesehen davon dass das kopierte objekt schon mit den werten vom orginal-objekt initialisiert wurde und man das eben nicht mehr machen muss?!
-
Man kopiert doch ein objekt "einfach" , indem man ein objekt erstellt und dann über das objekt den copy-konstruktor aufruft, oder?
klasse obj1;//orginal-objekt klasse obj2(klasse obj1);
(opj2= kopiertes objekt)
-
Ja, und genau das macht der Compiler automatisch im Kopierkonstruktor. Und sofern deine Attribute eine sinnvolle (sprich tiefe) Kopiersemantik haben, sind das auch jeweils tiefe Kopien.
Bei gutem Design hast du Klassen, die Ressourcen verwalten wie z.B. Listen, Vektoren, oder Bäume, technisch so abgekapselt und auch implementiert, dass du dich von außen nicht um das Kopieren selbst kümmern musst. Dann reicht sowas wie Widget b(a);. Und dadrauf sollte es auch hinauslaufen, denn der User oder Client kennt die Innereien deiner Klasse ja nicht oder muss sich erst in stundenlanger Kleinarbeit da reinarbeiten, was er nicht will und soll.
Und achja, es gibt keinen Unterschied zwischen Objekt und kopiertem Objekt. Der Unterschied ist nur, wie es initialisiert wird. (Das ist auch der Grund, warum es keinen "Kopierdestruktor" gibt, es sind einfach 2 völlig verschiedene und unabhängige Objekte, die nur 'zufällig' den gleichen Inhalt haben.)
Und in jedem Fall solltest du im Konstruktor ein Objekt so initialisieren, dass der Client es so benutzen kann wie es vorgesehen ist.
-
Skym0sh0 schrieb:
Und achja, es gibt keinen Unterschied zwischen Objekt und kopiertem Objekt. Der Unterschied ist nur, wie es initialisiert wird.
Wie kann ich denn dann entscheiden ob ich auf das kopierte oder auf das orginale objekt zugreifen kann?
wenn ich nur ein objekt habe und über den copy-konstruktor ein zweites "identisches" objekt erstelle, aber sowieso nur auf das orginale objekt zugreifen kann, was bringt mir dann eine kopie?(sorry wegen genauem nachfragen,schreibe bald n arbeit und befürchte dass was mit copy-konstruktoren gefordert wird)
-
Hä?
Wenn du ein hast und ein zweites erstellst und dabei den Kopierkonstruktor benutzt, dann kannst du auf beide zugreifen, je nachdem wie du es brauchst:
Widget w1; // ... do some work Widget w2(w1); // ist bzw. sollte gleich w1 sein, aber nicht identisch, also nicht dasselbe, nur dasgleiche // ... do further work w1.doSomeStuff(); // macht was mit w1 w2.doOtherStuff(); // macht was mit w2
Und der häufigste Nutzen ist erstmal bei Funktions aufrufen:
void foo(Widget w) { // w ist hier eine Kopie vom aufrufenden Objekt } int main() { Widget a; // do stuff foo(a); // ... }
Am besten spielst du mal etwas mit so einem Beispiel rum und versuchst es nachzuvollziehen. Achtung!! Es gibt noch sowas wie Call-By-Reference.
#include <iostream> class Widget { int id; public: Widget(int id) : id(id) { std::cout << "Constructed: " << this->id << std::endl; } Widget(Widget const& rhs) : id(-rhs.id) { std::cout << "CopyConstructed: " << this->id << " from " << rhs.id << std::endl; } ~Widget() { std::cout << "Deconstructed: " << this->id << std::endl; // Hoehoe, wortspiel :D } void status() const { std::cout << "I am: " << this->id << std::endl; } }; void foo(Widget w) { // w ist hier eine Kopie vom aufrufenden Objekt w.status(); } int main() { Widget a; a.status(); foo(a); a.status(); return 0; }
Edit: Mach dir keine Sorgen wegen dem Nachfragen, du hast Initiative mit dem Googeln gezeigt, du fragst spezifisch nach. Passt also alles, dafür ist ein Forum da :>
-
alles klar.
danke ich denke ich kann jetzt mit copy-konstruktoren etwas anfangen