Verbesserung an Template Klasse gegen Array Überlauf
-
otze schrieb:
es werden nur die zeiger kopiert, nicht die arrays die du vorher mit new angelegt hast. z2 und z1 benutzen dann das gleiche array zum lesen und schreiben, dh veränderungen bei z1 wirken sich auch bei z2 aus. Und das willst du doch nicht, oder?
Achsoooo, stimmt ja
der benutzer dieser Klasse will selber die möglichkeit haben einzugreifen, er muss dafür sorge tragen, dass er sein programm ordnungsgemäß beendet wenns nicht anders geht. durch exit verbaust du ihm diesen weg komplett.
Oder anders ausgedrückt: es ist nicht die aufgabe der Klasse, fehler abzufangen, der benutzer muss das tun.
-
const typ &operator[](int i)const;
--> Kannst mir wohl nicht auf die schnelle Erklärung wieso ich das auch so machen sollte ???
Und bis ich das Buch mir kaufe dauerts noch was. Muss erstmal mein 2. Buch C++ Entpackt zu Ende lesen.
-
Das bewirkt, dass du die Methode auch von einem Konstanten Objekt aus aufrufen kannst (das const am Ende). Nun haste aber das Problem, dass du bei einem nichtkonstanten Objekt auch nur lesezugriff auf die Elemente bekommst. Dazu wäre dann eine Überladung gut...
-
ness schrieb:
Das bewirkt, dass du die Methode auch von einem Konstanten Objekt aus aufrufen kannst (das const am Ende).
Aber was bringt mir das dann ???
const MyArray<int> zahl1(10); zahl1[5]=6;
Da ruft ein Konstantes Objekt die Elemtfunktion auf.
Aber der Compiler mekert, weil ich das Konstante Objekt ja nicht ändern darf...->Verstehe immer noch nicht genau, was ich damit:
const typ &operator[](unsigned int i) const;
machen soll
-
Freak_Coder schrieb:
ness schrieb:
Das bewirkt, dass du die Methode auch von einem Konstanten Objekt aus aufrufen kannst (das const am Ende).
Aber was bringt mir das dann ???
const MyArray<int> zahl1(10); zahl1[5]=6;
Da ruft ein Konstantes Objekt die Elemtfunktion auf.
Aber der Compiler mekert, weil ich das Konstante Objekt ja nicht ändern darf...Deswegen musst du ja auch zwei Versionen des []-Operators schreiben.
Allgemein schreibt man zwei Versionen des []-Operators um komfortabel mit ihm zu arbeiten. D.h. man schreibt einmal eine "read-only" []-Operator-Funktion (Diese ist auch für konstante Objekte aufrufbar) _und_ eine "normale" []-Operator-Funktion. Diese ist nur für nicht-konstante Objekte aufrufbar und darf deshalb die Werte ändern."normale" Version:
template <class typ> typ& MyArray<typ>::operator[](const unsigned int i) { if(i < aktSize) return array[i]; else throw out_of_range("Out of Range!"); }
"read-only" Version:
template <class typ> const typ& MyArray<typ>::operator[](const unsigned int i) const { // Rest wie gehabt... }
Nun zwei Anwendungsbeispiele:
Du hast einen konstantes Objekt vom Typ MyArray welches du per Ctor initalisierst und die Elemente auch mit einem Default-Wert versiehst. (Das sollte dein Ctor auch noch bereitstellen).Nun kannst du die Werte des Arrays natürlich nicht ändern, es ist jedoch sinnvoll die Werte wenigstens auszulesen. Dazu schreibst du dann beispielsweise:
std::cout << "\n v[0] vom Objekt v des Typs MyArray: " << v[0] << std::endl;
Und hier kommt die read-only Version deiner Operator-Funktion ins Spiel. Nur, weil du diese Operator-Funktion überladen hast (bzw. eine extra-read-only Funktion geschrieben hast), kannst du obige Zeile schreiben ohne einen Compile-Error zu bekommen.
Wenn du dagegen
v[0] = 1;
schreibst, wird die nicht read-only Funktion aufgerufen. (Natürlich darf dein Array dann nicht als const deklariert sein).
Kurz gesagt, du schreibst eine const und eine nicht-const Version einer Operator-Funktion, damit diese auch für konstante Objekte aufrufbar ist bzw. dass auf die einzelnen Elemente eines Arrays auch _nur_ lesend zugegriffen werden kann.
Caipi
-
Achso, thx.
Wenn der Konstruktor die Elemente schon initialisieren kann ist es ja sinnvoll.
Verstehe es endlich
-
Freak_Coder schrieb:
Achso, thx.
Wenn der Konstruktor die Elemente schon initialisieren kann ist es ja sinnvoll.
Verstehe es endlichdu meinst const?
nein.es geht um
void printAll(Array const& a){ //also a wird von dieser funktion nicht verändert, das ist hiermit versprochen cout<<a[0]<<endl;//geht net cout<<a[1]<<endl;//geht auch net cout<<a[2]<<endl;//geht immer noch net }
und da man arrays allenthalben als const-referenz übergibt (immer, wenn man kann!), ist es nötig, einen op[] anzubieten, der auch const-arrays kann.
-
volkard schrieb:
void printAll(Array const& a){ //also a wird von dieser funktion nicht verändert, das ist hiermit versprochen cout<<a[0]<<endl;//geht net cout<<a[1]<<endl;//geht auch net cout<<a[2]<<endl;//geht immer noch net }
Wieso sollte
cout<<a[1];
nicht funktionieren. Ich verändere das Objekt doch nicht ???
Und muss es in der Parameterliste nicht heißen:void printAll(const Array& a)
---EDIT-->>> Hmmm, habs gerade getestet. Funktioniert anscheinend beider sehr gut
-
Freak_Coder schrieb:
Wieso sollte
cout<<a[1];
nicht funktionieren. Ich verändere das Objekt doch nicht ???
weiß das auch der op[] ?
-
-
Hallo wiedermal,
habe noch ne Frage.Ich will jetzt nocheinen Konstruktor schreiben mit dem ich die Elemente inintialsieren möchte(wie Caipi es meinte).
Meine Template Klasse sieht jetzt so aus, mit size als nicht generischen Datentyp:
template <class typ,int size> class MyArray
Nun ist das Problem das ich nicht weiß wie der Ctor aussiehen soll.
MyArray(typ a,typ b,...);
So müsste es doch irgendwie sein. Aber wie greif ich jetzt auf die "..." zu und die Parameterliste sollte doch so lang sein wie in size angegeben.
Ich stelle mir das so vor das es am Ende so aussieht:
MyArray<int,3> zahlen(1,5,9)
-
das klappt so leider nicht, vorallem da die ellipse(...) nur mit pods klarkommt, dh komplexere datentypen kannst du da nicht reinpacken.
was du machen kannst ist einen start und endzeiger für ein array annehmen
int zahlenWerte[3]={1,2,3}; MyArray<int,3> zahlen(zahlenWerte,zahlenWerte+3);
oder vielleicht gleich so ein array erwarten:
int zahlenWerte[3]={1,2,3}; MyArray<int,3> zahlen(zahlenWerte);
für den constructor gibt es leider nicht soviele möglichkeiten, danach stehen einem dann aber viele möglichkeiten zur verfügung.
-
Hmmmm, finde ich nicht gerade sehr schön aber was solls
Dann mache ich es eben so...(vlt. finde ich ja ne vieeeeel bessere Lösung)
-
Nun endlich die komplett fertige Template - Klasse:
(Eine Frage noch im Code)template<class typ,int size> class MyArray { typ* array; int aktSize; void grow(int newSize); public: MyArray(); MyArray(const MyArray &Array_kopie); MyArray(typ* arr); typ operator=(MyArray a); typ &operator[] (unsigned int i); const typ &operator[](unsigned int i) const; ~MyArray() { delete [] array; } }; template<class typ,int size> MyArray<typ,size>::MyArray() { aktSize=size; array=new typ[size]; } template<class typ,int size> MyArray<typ,size>::MyArray(const MyArray &Array_kopie) { array=new typ[Array_kopie.aktSize]; aktSize=Array_kopie.aktSize; for(int i=0;i<Array_kopie.aktSize;i++) array[i]=Array_kopie.array[i]; } template<class typ,int size> MyArray<typ,size>::MyArray(typ* arr) { aktSize=size; array=new typ[size]; for(int i=0;i<size;i++) array[i]=arr[i]; } template <class typ,int size> typ &MyArray<typ,size>::operator[](unsigned int i) { if(i>aktSize) grow(i); return array[i]; } template <class typ,int size> const typ &MyArray<typ,size>::operator[](unsigned int i) const { return array[i]; /* Was soll ich hier machen wenn nen indexüberschreitung auftritt, einfach dann den UNSINNIGEN Wert ausgeben lassen, oder??? */ } template <class typ,int size> typ MyArray<typ,size>::operator=(MyArray a ) { delete [] array; array=new typ[a.aktSize]; for(int i=0;i<a.aktSize;i++) array[i]=a.array[i]; } template <class typ,int size> void MyArray<typ,size>::grow(int newSize) { typ* temparray; int altSize=aktSize; aktSize=newSize * 2; temparray=new typ [aktSize]; for(int i=0;i<aktSize;i++) temparray[i]=array[i]; delete [] array; array=temparray; }
-
Freak_Coder schrieb:
template <class typ,int size> const typ &MyArray<typ,size>::operator[](unsigned int i) const
{
return array[i]; /* Was soll ich hier machen wenn nen indexüberschreitung auftritt,
einfach dann den UNSINNIGEN Wert ausgeben lassen, oder??? */}
Bei ungültigem Index würde ich eine Exception vom Typ out_of_range werfen.
Caipi
-
Der op= ist noch ein bißchen teuer im Moment. Das Argument wird by value übergeben, das heißt Du ziehst erst ne Kopie um die danach zu kopieren... und gibst dann nochmal ne Kopie zurück. Da sollte auf jeden Fall ne Referenz hin.
Lösung: entweder auf Copy & swap umsteigen oder by reference übergeben, dann aber auf Selbszuweisung achten.
-
Lösung: entweder auf Copy & swap umsteigen oder by reference übergeben, dann aber auf Selbszuweisung achten.
oder erst auf selbst zuweisung achten und dann copy and swap
das sähe dann so aus:
template <class typ,int size> typ MyArray<typ,size>::operator=(const MyArray& a ) { if(this!=&a){ MyArray temp(a); std::swap(array,temp.array); std::swap(aktSize,temp.aktsize); } }
-
otze schrieb:
oder erst auf selbst zuweisung achten und dann copy and swap
gleich copy&swap ist besser. weil selbstzuweisungen echt so selten sind, daß man sich aus performance-sicht den test sparen sollte. und notwendig isser ja nicht bei copy&swap.
-
otze schrieb:
template <class typ,int size> typ MyArray<typ,size>::operator=(const MyArray& a ) { if(this!=&a){ MyArray temp(a); std::swap(array,temp.array); std::swap(aktSize,temp.aktsize); } }
Wieso muss ich hier ne extra Kopie von "a" namens "temp" erstellen und kann nicht direkt swap anwenden ???
-
weil a keine kopie sondern eine referenz auf das als parameter übergebene objekt ist, darf nicht einfach swap benutzt werden, weil sonst keine wertekopie sondern ein einfacher werteaustausch stattfinden würde.
dh wenn du a=b machen würdest, würde ohne die kopie a den wert von b haben, b aber den alten wert von a!