kopie einer referenz?
-
Das
m_feldist weder ein Array noch eine Referenz, sondern ein Zeiger. Das heisst, er zeigt irgendwo hin, wo die Daten gespeichert sind (zumindest sollte er das, oder auf Null).Du musst also Speicher bereitstellen.
// Konstruktor der Klasse MyClass::MyClass(int Size) : m_feld(new T[Size]) // Array in der Initialisierungsliste anlegen , m_size(Size) { } // Destruktor MyClass::~MyClass() { delete[] m_feld; // Speicher im Destruktor freigeben } // Kopierkonstruktor MyClass::MyClass(const MyClass& Origin) : m_feld(new T[Origin.m_size]) // Dynamisches Array mit der Grösse des Originals anfordern , m_size(Origin.m_size) { std::copy(Origin.m_feld, Origin.m_feld + Origin.m_size, m_feld); // Inhalte kopieren // Eventuell könnte man auch std::uninitialized_copy() // und operator new verwenden } // Zuweisungsoperator MyClass& MyClass::operator= (const MyClass& Origin) { MyClass Temp(Origin); // temporäres Objekt über Kopierkonstruktor initialisieren swap(Temp); // schau mal nach Copy&Swap-Idiom }
-
MFK schrieb:
Hackfresser schrieb:
CFeld hat als Membervariable ein array:
private: T* m_feld;Das ist ein Zeiger, kein Array.
Vorweg: Ich habe CFeld.h nicht selbst geschrieben, sondern vorgegeben bekommen, habe es allerdings etwas erweitert bisher.
Mir ist anscheinend der Zusammenhang zwischen array und Zeiger nicht klar. in der Klasse selbst wird ganz zwangslos sowas wiereturn m_feld[index];benutzt. Bisher nahm ich an, dass array und Zeiger eigentlich dasselbe ist.
Wenn ich versuche, m_feld in ein array zu ändern, also
private: T m_feld[Max];...bekomme ich im Konstruktor bei der Zeile
m_feld = new T[Max];die Fehlermeldung
error C2440: '=' : cannot convert from 'int *' to 'int [20]'
Warum geht das nicht? Und wo ist der Unterschied zwischen einem Zeiger und einem array?
MFK schrieb:
Und dein Problem hat mit Referenzen nichts zu tun.
Vielleicht habe ich das etwas unübersichtlich präsentiert, ich habe 2 Klassen:
CFeld<T, Max> war vorgegeben und hat ein array und dessen Länge als member
CQuickSort<T, Max> hab ich selbst geschrieben und hat eine Funktion, die als Parameter eine Referenz auf ein CFeld nimmt:void sort(CFeld<T, Max>& feld)MFK schrieb:
Deiner Klasse fehlen Copykonstruktor und Zuweisungsoperator. Einfacher wäre es aber, aus diesem Zeiger einen vector<T> zu machen. Dann geht alles von ganz allein.
Den Copykonstruktor hab ich geschrieben, der funktioniert auch, glaube ich.
Aber wieso muss ich dann noch den Zuweisungsoperator überschreiben? Ich dachte, dass der standardmässig gerade den Kopierkonstruktor aufruft
-
Hackfresser schrieb:
Wenn ich versuche, m_feld in ein array zu ändern, also
private: T m_feld[Max];...bekomme ich im Konstruktor bei der Zeile
m_feld = new T[Max];die Fehlermeldung
error C2440: '=' : cannot convert from 'int *' to 'int [20]'
Warum geht das nicht?
weil ein array T[Max] nun mal kein zeiger ist.
Und wo ist der Unterschied zwischen einem Zeiger und einem array?
-
Hackfresser schrieb:
Den Copykonstruktor hab ich geschrieben, der funktioniert auch, glaube ich.
Aber wieso muss ich dann noch den Zuweisungsoperator überschreiben? Ich dachte, dass der standardmässig gerade den Kopierkonstruktor aufruftNein. Standardmässig kopiert er memberweise, also nur die Zeiger selber. Schau dir doch einfach mal meinen Code an...
-
Nexus schrieb:
Nein. Standardmässig kopiert er memberweise, also nur die Zeiger selber.
Ok...verstehe ich das richtig, wenn ich weder Kopierkonstrukter noch Zuweisungsoperator explizit implementiere, werden beide vom Compiler erstellt und tun das gleiche, nämlich alle Membervariablen kopieren? Und falls ja, werden die Member dann wiederum mit ihrem eigenen Kopierkonstruktor kopiert, oder mit ihrem eigenen Zuweisungsoperator?
-
Hackfresser schrieb:
Ok...verstehe ich das richtig, wenn ich weder Kopierkonstrukter noch Zuweisungsoperator explizit implementiere, werden beide vom Compiler erstellt und tun das gleiche, nämlich alle Membervariablen kopieren?
Ja, sofern das geht. Hat man Referenzen oder konstante Objekte als Member, kann kein Zuweisungsoperator generiert werden. Hat man nichtkopierbare Objekte als Member, kann zusätzlich kein Kopierkonstruktor generiert werden.
Hackfresser schrieb:
Und falls ja, werden die Member dann wiederum mit ihrem eigenen Kopierkonstruktor kopiert, oder mit ihrem eigenen Zuweisungsoperator?
Ja. PODs haben zwar keine Konstruktoren, werden also bitweise kopiert.
-
Nexus schrieb:
Ja, sofern das geht. Hat man Referenzen oder konstante Objekte als Member, kann kein Zuweisungsoperator generiert werden.
Hm... Ich hab mir mal ne Klasse mit Referenzen und konstanten Objekten als Member geschrieben, und da scheint der Zuweisungsoperator einwandfrei zu funktionieren, also:
class CTest { public: CTest (int a, MyInt b, MyInt& c) : _a(a), _b(b), _c(c){}; //... const int _a; const MyInt _b; MyInt& _c; };In der main kann ich dann problemlos sowas machen:
MyInt ref(3); CTest t1(1, MyInt(2), ref); CTest t2 = t1;Nexus schrieb:
Hat man nichtkopierbare Objekte als Member, kann zusätzlich kein Kopierkonstruktor generiert werden.
Wann ist denn ein Objekt nicht kopierbar? Wir hatten in der VL ein Entwurfsmuster namens "Singleton", da wurde dann Kopierkonstruktor und Zuweisungsoperator privat deklariert, aber nicht implementiert, aber gibt es noch andere Möglichkeiten?
Nexus schrieb:
Hackfresser schrieb:
Und falls ja, werden die Member dann wiederum mit ihrem eigenen Kopierkonstruktor kopiert, oder mit ihrem eigenen Zuweisungsoperator?
Ja.
Ja, Kopierkonstruktor, oder Ja, Zuweisungsoperator?
-
powiwe schrieb:
Hackfresser schrieb:
Wenn ich versuche, m_feld in ein array zu ändern, also
private: T m_feld[Max];...bekomme ich im Konstruktor bei der Zeile
m_feld = new T[Max];die Fehlermeldung
error C2440: '=' : cannot convert from 'int *' to 'int [20]'
Warum geht das nicht?
weil ein array T[Max] nun mal kein zeiger ist.
Ich verstehe es immer noch nicht. Ich kann mir ein array machen:
int a[3] = {1,2,3};dann ist doch a ein Zeiger auf das erste Element?
cout << a;gibt mir doch ne Adresse aus, genau wie auch
int* b = new int; cout << b;weiterhin kann ich mit a rechnen wie mit einem Zeiger, und auch b kann ich ansprechen wie ein array:
*(b+1) = 20; cout << *(a+1); // 2 cout << b[1]; // 20Hat es vielleicht etwas mit dynamischem und automatischem Speicher zu tun?
Und wieso kann ich für die Grösse eines arrays keine Variable benutzen?int a = 3; int b[a] // FehlerIm dynamischen Speicher ist das ja kein Problem:
int* d = new int[a];
-
Hackfresser schrieb:
In der main kann ich dann problemlos sowas machen:
MyInt ref(3); CTest t1(1, MyInt(2), ref); CTest t2 = t1;Die letzte Zeile ist eine Initialisierung, keine Zuweisung.
Hackfresser schrieb:
Ich verstehe es immer noch nicht. Ich kann mir ein array machen:
int a[3] = {1,2,3};dann ist doch a ein Zeiger auf das erste Element?
Nein. a ist der Name des Arrays. Der ist implizit in einen rvalue-int-Zeiger konvertierbar, der auf das erste Element zeigt.
-
Hackfresser schrieb:
Ich verstehe es immer noch nicht. Ich kann mir ein array machen:
int a[3] = {1,2,3};dann ist doch a ein Zeiger auf das erste Element?
Nein,
aist der Name des Arrays.
Der Unterschied wird auch so ersichtlich:int a[3] = {1,2,3}; std::cout << sizeof(a) << '\n' //gibt 3 * sizeof(int) aus (vermutlich 12) int* pa = a; //hier wird a implizit in einen Pointer konvertiert std::cout << sizeof(pa) << '\n' //gibt die groesse von int* aus (vermutlich 4) ++a; //Fehler, man kann keine Pointerarithmetik mit Arrays machen, da Arrays keine Pointer sind ++pa; //geht -> Pointerarithmetikcout << a;gibt mir doch ne Adresse aus, genau wie auch...
Ja, weils implizit in einen Pointer konvertiert wird.
int* b = new int; cout << b;Hier ist b wirklich ein Pointer.
weiterhin kann ich mit a rechnen wie mit einem Zeiger, und auch b kann ich ansprechen wie ein array:
cout << *(a+1); // 2Dabei wird ein temporärer Pointer erstellt, dieser wird um 1 erhöht und dann dereferenziert. a bleibt aber weiterhin das Array.
`a = a + 1; //oder ++a -> FEHLERstd::cout << *a
`
geht hingegen nicht.
-
Hackfresser schrieb:
In der main kann ich dann problemlos sowas machen:
MyInt ref(3); CTest t1(1, MyInt(2), ref); CTest t2 = t1;Das liegt daran, dass wenn der Zuweisungsoperator in der Deklaration vorkommt, es nicht wirklich der Zuweisungsoperator, sondern der Kopierkonstruktor ist, der aufgerufen wird.
CTest t2(t1);wäre das Gleiche. Die Schreibweise mit = geht auch nur bei nicht expliziten Konstruktoren.
Hackfresser schrieb:
Ja, Kopierkonstruktor, oder Ja, Zuweisungsoperator?
Beim standardgenerierten Kopierkonstruktor werden die Member mit ihrem Kopierkonstruktor kopiert, beim Zuweisungsoperator werden sie mit ihrem Zuweisungsoperator zugewiesen.
-
Vorweg erstmal Danke für die hilfreichen Antworten.
Dann noch ein paar Fragen:Hat man Referenzen oder konstante Objekte als Member, kann kein Zuweisungsoperator generiert werden.
Ok... aber warum ist das so?
Und überhaupt, warum gibts bei C++ die Unterscheidung von Kopie und Zuweisung?
Mal ganz naiv: bei den Übungsprogrämmchen die ich bisher schreiben musste war die Funktion von Kopierktor und Zuweisung immer gleich, soweit ich das überblicken kannOffen ist auch noch meine Frage, warum man Arrays auf dem Stack nicht mit variabler Grösse initialisieren kann...
Ausserdem: von Java bin ich es gewohnt, Objekte deklarieren zu können, ohne sie Initialisieren zu müssen, bzw sie werden auf NULL initialisiert, also:
MeineKlasse instanz;(Ich hoffe das stimmt so, Java ist für mich laange her.)
Wenn ich das bei C++ versuche:CMeineKlasse instanz;...versucht er immer gleich, den default-Ktor aufzurufen? Warum ist das so?
-
Hackfresser schrieb:
Hat man Referenzen oder konstante Objekte als Member, kann kein Zuweisungsoperator generiert werden.
Ok... aber warum ist das so?
Wenn du eine Klasse mit konstanten Membern (Referenzen sind immer konstant) zuweisen willst, müssen ja alle Member zugewiesen werden. Da einem konstanten Wert per Definition nichts zugewiesen werden kann, ist die Zuweisung der gesamten Klasse nicht möglich (zumindest beim standardgenerierten
operator=).Hackfresser schrieb:
Und überhaupt, warum gibts bei C++ die Unterscheidung von Kopie und Zuweisung?
Mal ganz naiv: bei den Übungsprogrämmchen die ich bisher schreiben musste war die Funktion von Kopierktor und Zuweisung immer gleich, soweit ich das überblicken kannKopie beinhaltet die Erstellung eines neuen Objekts. Das Objekt kann gerade mit den kopierten Werten initialisiert werden. Zuweisung hingegen verändert den Status eines bereits bestehenden Objekts.
Hackfresser schrieb:
Offen ist auch noch meine Frage, warum man Arrays auf dem Stack nicht mit variabler Grösse initialisieren kann...
C++ hat dafür diesen Weg eingelegt. Möglich wäre es (->
alloca()), aber es ist auch meist sinnvoll, dafür den Heap zu benutzen. Vor allem weil dynamische Arrays oft grösser werden können und der Stack in seiner Grösse beschränkt ist. Aber mit der STL kann man dynamische Container sowieso viel komfortabler als jede Form von rohen Arrays benutzen, von daher ist die Einschränkung ertragbar.Hackfresser schrieb:
...versucht er immer gleich, den default-Ktor aufzurufen? Warum ist das so?
Das ist auch eine Eigenschaft von C++. Willst du nur einen Verweis auf ein Objekt anlegen und das später initialisieren, kannst du Zeiger benutzen:
CMeineKlasse* Zeiger = 0; // zur Sicherheit mit Null initialisieren, um Ungültigkeit kennzuzeichnen // ... mache etwas Zeiger = new CMeineKlasse(/* Konstruktorargumente */); // ... verwende Zeiger delete Zeiger;
-
Sehr gut, danke Leute!
Ich habe mit dem, was Ihr mir alles erklärt habt sogar meine ursprüngliche Frage selbst beantworten können.
-
Hackfresser schrieb:
Ausserdem: von Java bin ich es gewohnt, Objekte deklarieren zu können, ohne sie Initialisieren zu müssen, bzw sie werden auf NULL initialisiert, also:
In C++ gibt es eigentlich keine ungültige Objekte. Das, was am nächsten kommt ist der Zeiger, der 0 ist. Das ist allerdings ein gültiger Zeiger. Er zeigt auf kein Objekt, aber das Objekt ansich ist gültig. Die "Ungültigkeit" kommt vom Betrachter her, der dann sagt, dass man an eine Funktion keinen 0-Zeiger übergeben darf oder wie auch immer. Aber man kann durchaus auch mit 0 als Zeiger rechnen. Insbesondere Klasse Instanzen haben keinen Zustand, der mit NULL beschrieben werden kann. Die Ungültigkeit kommt auch hier wieder vom Programmierer aus, der sagt, dass eine bestimmte Klasse eine Invariante haben muss und ansonsten ist sie ungültig. (Und wirft allenfalls eine exception).