Vererbung von Vector<T> an NumericVector<T> Probleme mit operator* u.ä.
-
d.h. der operator= den der compiler in
[code]numericvector.h:12: sieht ist garnicht der denn ich in Vector<T> definiert habe sonder einer den der Compiler selbst erzeugt hat und der zufällig das macht was ich erwarte???
-
Ich hab das grad mal getestet.
Einfach eine "Hallo ich bin's" Ausgabe in Vector<T>::operator=()
eingebaut.
Der NumericVector<T>::operator= meldet sich genauso.
nehme ich also an mein Compiler hat den doch erkannt und umgebaut.
Warum sollte der den er selbst erzeugt auch diese Ausgabe machen???
-
cris492 schrieb:
Hallo bin recht neu in C++ und auf ein Problem gestossen.
Ich hab eine rudimentäres Schuldbewusstsein, es entzieht sich aber meiner Erkenntnis wie ich meinem Rechenknecht elegant auf die Sprunge helfen kann.Hab eine Klasse Vector geschrieben mit operator= und operator*.
Hab eine Klasse NumericVector die public von Vector erbt.
Hab das auch mal auf das Nötigste reduziert.operator* erzeugt neuen Vector offensichtlich erzeugt auch die abgeleitete Methode
Numericvector::operator* einen Vector und keinen Num... .
die ersten beiden Anweisungen in main sind okay und dann meckert der Comp.
Auch sonst bin ich gern an Tips und Verbesserungsvorschlägen interessiert wenn sich schon mal jemand die Zeit nimmt das anzusehen.

Vielen Dank
[code][/code]hier gibt es mehrere grössere probleme.
1.zunächst einmal ist öffentliche vererbung von nicht-abstrakten klassen fast immer ein (design-)fehler (bst. idiomatische ausnahmen wie z.b. policyklassen lassen wir mal aussen vor). öffentliche vererbung heisst IST-EIN.
ist ein NumericVector ein Vector? (aus dem reduzierten code heraus ist das schwer zu entscheiden) - wenn das so ist, dann muss alles, was mit einem Vector möglich ist, auch mit NumericVector möglich sein. nun kann man ein Vector<T> jedem anderen Vector<T> zuweisen. dann muss ihn aber auch einem NumericVector<T> zuweisen können, denn ein NumericVector IST EIN Vector<T> (wäre Vector abstrakt lägen die dinge anders; da es keine instanzen von abstrakten klassen gibt, erübrigte sich diese bedingung). die erste frage, die du beantworten musst, ist also, ob das wirklich das ziel ist.
2.template<typename U> Vector(COUNTER n, U initVal); zwar ist gegen template konstruktoren im grunde nichts einzuwenden, in diesem falle ist das allerdings nicht hilfreich. die einzigen anforderungen, die an U gestellt werden, sind:
U ist CopyConstructible
U ist implizit konvertierbar in T
unter diesen bedingungen kann ein non-template konstruktor verwendet werden:
Vector(COUNTER n, const T& initVal) (die bedingungen sind hier sogar noch abgeschwächt, denn U muss nicht mehr CopyConstructible sein - und die Anforderung an T, CopyConstructible zu sein wird auch noch in anderen code-teilen gefordert, also nichts neues hier). ein non-template konstruktor hat viele vorteile: du sparst code, denn es gibt nur eine instanz; und falls dir ein fehler unterläuft, bekommst du sofort eine fehlermeldung, dort wo der konstruktor aufgerufen wird, und nicht irgendwo im konstruktor selbst.
3.while (vectorPtr == NULL && counter > 0){ --counter; vectorPtr = new T[dim]; } if (vectorPtr == NULL) cout << "\nnot enough memory!\n";new liefert niemals einen null pointer. falls die allokation fehlschlägt, wird eine std::bad_alloc exception geworfen (oder eine beliebige andere, die durch einen handler der form catch( std::bad_alloc ) gefangen werden kann).
4. Vector<T>& operator=(const Vector<T> &src);
wenn du einen selbstdefinierten assignment operator brauchst, wirst du auch einen selbst definierten copy-ctor benötigen (tatsächlich bietet sich hier für das assignment das copy&swap-idiom an).
5. template<typename T> template<typename U> Vector<T> Vector<T>::operator*(const U &scalar)
es dürfte sinnvoll sein, einen operator = zu definieren, und darauf basierend 2mal operator zu implementieren (je nachdem, auf welcher seite der scalar steht)auf das problem mit dem vererbten operator* gehe ich hier nicht ein, denn ich habe starke zweifel, dass vererbung hier überhaupt genutzt werden sollte. auf jeden fall benötigst du dann einen konvertierungs-ctor, der einen Vector<T> in einen NumericVector<T> konvertiert - und dann würde es auch mit deinem code funktionieren (ob es das ist, was du willst, ist eine andere frage).
-
camper schrieb:
cris492 schrieb:
Hallo bin recht neu in C++ und auf ein Problem gestossen.
Ich hab eine rudimentäres Schuldbewusstsein, es entzieht sich aber meiner Erkenntnis wie ich meinem Rechenknecht elegant auf die Sprunge helfen kann.Hab eine Klasse Vector geschrieben mit operator= und operator*.
Hab eine Klasse NumericVector die public von Vector erbt.
Hab das auch mal auf das Nötigste reduziert.operator* erzeugt neuen Vector offensichtlich erzeugt auch die abgeleitete Methode
Numericvector::operator* einen Vector und keinen Num... .
die ersten beiden Anweisungen in main sind okay und dann meckert der Comp.
Auch sonst bin ich gern an Tips und Verbesserungsvorschlägen interessiert wenn sich schon mal jemand die Zeit nimmt das anzusehen.

Vielen Dank
[code][/code]hier gibt es mehrere grössere probleme.
1.zunächst einmal ist öffentliche vererbung von nicht-abstrakten klassen fast immer ein (design-)fehler (bst. idiomatische ausnahmen wie z.b. policyklassen lassen wir mal aussen vor). öffentliche vererbung heisst IST-EIN.
ist ein NumericVector ein Vector? (aus dem reduzierten code heraus ist das schwer zu entscheiden) - wenn das so ist, dann muss alles, was mit einem Vector möglich ist, auch mit NumericVector möglich sein. nun kann man ein Vector<T> jedem anderen Vector<T> zuweisen. dann muss ihn aber auch einem NumericVector<T> zuweisen können, denn ein NumericVector IST EIN Vector<T> (wäre Vector abstrakt lägen die dinge anders; da es keine instanzen von abstrakten klassen gibt, erübrigte sich diese bedingung). die erste frage, die du beantworten musst, ist also, ob das wirklich das ziel ist.Wenn ich das richtig verstehe ja.
Alles was ein Vector kann soll auch ein NumericVector haben und können und mehr.
mit diesen abstrakten Klassen muss ich mich noch beschäftigen.camper schrieb:
2.template<typename U> Vector(COUNTER n, U initVal); zwar ist gegen template konstruktoren im grunde nichts einzuwenden, in diesem falle ist das allerdings nicht hilfreich. die einzigen anforderungen, die an U gestellt werden, sind:
U ist CopyConstructible
U ist implizit konvertierbar in T
unter diesen bedingungen kann ein non-template konstruktor verwendet werden:
Vector(COUNTER n, const T& initVal) (die bedingungen sind hier sogar noch abgeschwächt, denn U muss nicht mehr CopyConstructible sein - und die Anforderung an T, CopyConstructible zu sein wird auch noch in anderen code-teilen gefordert, also nichts neues hier). ein non-template konstruktor hat viele vorteile: du sparst code, denn es gibt nur eine instanz; und falls dir ein fehler unterläuft, bekommst du sofort eine fehlermeldung, dort wo der konstruktor aufgerufen wird, und nicht irgendwo im konstruktor selbst.Was bedeutet die Eigenschaft CopyConstructible zu haben.
Recht hast Du ich will eigentlich nur das ein double Vector auch mit float oder int Werte initialisiert werden kann. An dieser Stelle würde nach deiner Methode der Compiler implizite casts einbauen die das machen was ich will nur schöner???camper schrieb:
while (vectorPtr == NULL && counter > 0){ --counter; vectorPtr = new T[dim]; } if (vectorPtr == NULL) cout << "\nnot enough memory!\n";new liefert niemals einen null pointer. falls die allokation fehlschlägt, wird eine std::bad_alloc exception geworfen (oder eine beliebige andere, die durch einen handler der form catch( std::bad_alloc ) gefangen werden kann).
4. Vector<T>& operator=(const Vector<T> &src);
wenn du einen selbstdefinierten assignment operator brauchst, wirst du auch einen selbst definierten copy-ctor benötigen (tatsächlich bietet sich hier für das assignment das copy&swap-idiom an).Ich habe einen selbst definierten copy-ctor, aber rausgenommen um nicht ein ewig langes Programm zu posten. was ist das copy&swap-idiom???
camper schrieb:
5. template<typename T> template<typename U> Vector<T> Vector<T>::operator*(const U &scalar)
es dürfte sinnvoll sein, einen operator = zu definieren, und darauf basierend 2mal operator zu implementieren (je nachdem, auf welcher seite der scalar steht)Mit *= verändere ich doch mein aktuelles Objekt, was ich doch im Allgemeinen garnicht tun möchte.
camper schrieb:
auf das problem mit dem vererbten operator* gehe ich hier nicht ein, denn ich habe starke zweifel, dass vererbung hier überhaupt genutzt werden sollte. auf jeden fall benötigst du dann einen konvertierungs-ctor, der einen Vector<T> in einen NumericVector<T> konvertiert - und dann würde es auch mit deinem code funktionieren (ob es das ist, was du willst, ist eine andere frage).
Ja ich glaube das will ich im Moment.
Kann gut sein das sich das in Zukunft wieder ändert aber dann hab ich auch was gelernt.Vielen Dank
-
cris492 schrieb:
Der NumericVector<T>::operator= meldet sich genauso.
nehme ich also an mein Compiler hat den doch erkannt und umgebaut.
Warum sollte der den er selbst erzeugt auch diese Ausgabe machen???Das liegt daran, WIE der Compiler den Default-Assign zusammenbaut: Er ruft erst den operator= für die Basisklasse auf und weist dann (mit deren operator=) alle nichtstatischen Member zu, die die Klasse selber angelegt hat.
Alles was ein Vector kann soll auch ein NumericVector haben und können und mehr.
Und dein Code verletzt diese Bedingung schon - ein Vector kann mit beliebigen Typen initialisiert werden, ein NumericVector nicht

Was bedeutet die Eigenschaft CopyConstructible zu haben.
Ganz einfach: Der Typ muß einen öffentlich zugänglichen Kopier-Konstruktor besitzen.
Recht hast Du ich will eigentlich nur das ein double Vector auch mit float oder int Werte initialisiert werden kann. An dieser Stelle würde nach deiner Methode der Compiler implizite casts einbauen die das machen was ich will nur schöner???
Was hast du gegen implizite Casts? (btw, erstaunlich ist, daß du beim NumericVector genau diese Methode verwendet hast)
Mit *= verändere ich doch mein aktuelles Objekt, was ich doch im Allgemeinen garnicht tun möchte.
Gemeint war wohl eher so etwas:
//Mult-Assign (i.a. effizienter als Multiplikation) Vector<T>& Vector<T>::operator*=(T val){...} //Multiplikation (nutzt op*=) Vector<T> operator*(const Vector<T>& rhs,T lhs) { Vector<T> ret=rhs; return ret*=lhs; }camper schrieb:
auf das problem mit dem vererbten operator* gehe ich hier nicht ein, denn ich habe starke zweifel, dass vererbung hier überhaupt genutzt werden sollte. auf jeden fall benötigst du dann einen konvertierungs-ctor, der einen Vector<T> in einen NumericVector<T> konvertiert - und dann würde es auch mit deinem code funktionieren (ob es das ist, was du willst, ist eine andere frage).
Ja ich glaube das will ich im Moment.
Wenn du jeden Vector als NumericVector verwenden willst, wieso definierst du dann überhaupt zwei verschiedene Typen?
-
CStoll schrieb:
cris492 schrieb:
Alles was ein Vector kann soll auch ein NumericVector haben und können und mehr.
Und dein Code verletzt diese Bedingung schon - ein Vector kann mit beliebigen Typen initialisiert werden, ein NumericVector nicht

Verstehe ich jetzt nicht wie Du das meinest ich kann doch bei beiden mit beliebigen Datentypen initialisieren und rechenen, oder wo geht mir das verloren?
CStoll schrieb:
Was hast du gegen implizite Casts? (btw, erstaunlich ist, daß du beim NumericVector genau diese Methode verwendet hast)
Ja ich mache das auf den Rat hin auch genau so und wie gesagt recht hat er, dass macht so genau was ich will.
CStoll schrieb:
Gemeint war wohl eher so etwas:
//Mult-Assign (i.a. effizienter als Multiplikation) Vector<T>& Vector<T>::operator*=(T val){...} //Multiplikation (nutzt op*=) Vector<T> operator*(const Vector<T>& rhs,T lhs) { Vector<T> ret=rhs; return ret*=lhs; }Ja das ist mir nach etwas überlegung dann auch aufgegangen wie er das gemeint haben muss. Da ich ja bei + und * usw. sowieso ein neues Objekt erzeuge kann ich auch gleich auf += und *= aufbauen.
CStoll schrieb:
Wenn du jeden Vector als NumericVector verwenden willst, wieso definierst du dann überhaupt zwei verschiedene Typen?
Das liegt daran das in der Ingenieursmathematik in verschiedensten Zusammenhängen Vektoren gebraucht werden. z.B. solche die man als echte Vektoren bezeichnen würde da sie Elemente eines Vektorraums sind und man mit ihnen Gleichungssysteme lösen will und andere wie Zeitreihen die ganz andere Objekte sind aber im Rechner gleich gespeichert werden und die meisten Operatoren das selbe machen. Allen gemein ist nur das array was im Rechner dahintersteht.
Der konkrete Anlass für mich war aber tatsächlich ein anderes Problem, wo Du bestimmt noch eine wetvolle Einsicht zu hast.
Ich hab eine Klasse Vector und eine Klasse Matrix.
In der Klasse Matrix gibt es einen op* der Vector = Matrix * Vector macht, also die Klasse Vector schon kenne muss.
In der Klasse Vector gibt es jetzt eine Funktion LGS, der eine Matrix und ein Vector übergeben wird und die dann mit x.LGS(A,b) ein x findet so das A*x = b ist.
Mit meine Kentnissen geht das so nicht. da die Klassen aufeinander aufbauen.
Für die genannten Funktionen muss jeweils die andere Klasse schon bekannt sein.Gibt's da eine Trick?
-
cris492 schrieb:
CStoll schrieb:
Wenn du jeden Vector als NumericVector verwenden willst, wieso definierst du dann überhaupt zwei verschiedene Typen?
Das liegt daran das in der Ingenieursmathematik in verschiedensten Zusammenhängen Vektoren gebraucht werden. z.B. solche die man als echte Vektoren bezeichnen würde da sie Elemente eines Vektorraums sind und man mit ihnen Gleichungssysteme lösen will und andere wie Zeitreihen die ganz andere Objekte sind aber im Rechner gleich gespeichert werden und die meisten Operatoren das selbe machen. Allen gemein ist nur das array was im Rechner dahintersteht.
Jetzt verstehe ich die Zusammenhänge überhaupt nicht mehr. Wenn du nur die gleiche interne Darstellung benötigst, ist öffentliche Vererbung der falsche Weg.
Der konkrete Anlass für mich war aber tatsächlich ein anderes Problem, wo Du bestimmt noch eine wetvolle Einsicht zu hast.
Ich hab eine Klasse Vector und eine Klasse Matrix.
In der Klasse Matrix gibt es einen op* der Vector = Matrix * Vector macht, also die Klasse Vector schon kenne muss.
In der Klasse Vector gibt es jetzt eine Funktion LGS, der eine Matrix und ein Vector übergeben wird und die dann mit x.LGS(A,b) ein x findet so das A*x = b ist.
Mit meine Kentnissen geht das so nicht. da die Klassen aufeinander aufbauen.
Für die genannten Funktionen muss jeweils die andere Klasse schon bekannt sein.Gibt's da eine Trick?
forward-Deklarationen:
class Vector; class Matrix { public: Matrix Inv(){...} //nur deklariert - hier reicht der Name: Vector operator*(const Vector& lhs); }; class Vector { public: Vector LGS(const Matrix& A,const Vector& b) { return A.Inv()*b; } }; //Definition - benötigt komplette Klassendeklaration Vector Matrix::operator*(const Vector& lhs) {...}
-
Das nach Definition und Deklaration zu trennen und ineinander zu schachteln werde ich mal testen.
CStoll (off) schrieb:
Jetzt verstehe ich die Zusammenhänge überhaupt nicht mehr. Wenn du nur die gleiche interne Darstellung benötigst, ist öffentliche Vererbung der falsche Weg.
Wieso das denn? Ist es nicht nahe liegend das ich die allen gemeinsamen Eigenschaften, hier nur die länge (im Speicher), und die allen gemeinsamen Funktionen, anzeigen, op*=, op*, op+, op() u.ä. nur einmal schreibe und dann durch Vererbung und Spezialisierung die speziellen Klassen die ich dann eigentlich erst benutzen will erzeuge? Ich dachte das ist die eigentliche Anwendung der Vererbung. Allgemeines einmal und Spezielles dann wo gebraucht implementieren.
mfG, cris492
-
Ja, aber du bringst dort etwas durcheinander, wer von was erbt. Zeitreihen (Vector) und mathematische Vektoren (NumericVector) verwenden zwar die selbe Datendarstellung, aber ansonsten besteht keine direkte Vererbungsbeziehnung zwischen ihnen - deshalb sollten sie eher beide von einer gemeinsamen Basisklasse (BaseVector) abgeleitet werden als direkt voneinander.
-
Oh das hab ich nie erwähnt.
Vector soll die Basisklasse sein.
Zeitreihenvector u.ä. gibt es noch garnicht.
-
Achso. Trotzdem ist dein Design verbesserungswürdig. Öffentliche Vererbung stellt ein "ist-ein" dar, um ein "ist-implementiert-mit" bzw. "verwendet" zu kennzeichnen, verwendest du besser private Vererbung oder Aggregation.
Btw, wenn du die Zeitreihen-Vektoren dazunimmst, wirst du dir wünschen, keine Umwandlung von Vektor nach NumericVector zu haben

-
CStoll schrieb:
Achso. Trotzdem ist dein Design verbesserungswürdig. Öffentliche Vererbung stellt ein "ist-ein" dar, um ein "ist-implementiert-mit" bzw. "verwendet" zu kennzeichnen, verwendest du besser private Vererbung oder Aggregation.
Aber bei privater Vererbung kann ich ja meine BasisFunktionen nicht mehr so einfach durchreichen. Und was ist denn bitte Aggregation, hab ich ja noch nie gehört.
mfG cris492
-
cris492 schrieb:
Aber bei privater Vererbung kann ich ja meine BasisFunktionen nicht mehr so einfach durchreichen.
Wie du gesehen hast, klappt das selbst bei öffentlicher Vererbung nicht so einfach

Und was ist denn bitte Aggregation, hab ich ja noch nie gehört.
"hat-ein"-Beziehung (Elementzuordnung).
-
Ja, aber was da bei der öffentlichen Vererbung nicht geklappt hat, läuft nun super mit dem Konvertierungskonstrucktor. Das Problem war ja das bestimmte operatoren ein neu erzeugtes Objekt zurückgeben und der op= dann gemeckert hat.
Es scheint mir eine gute Lösung mit dem KonvertCtor.Vielen Dank
-
Dann warte mal ab, bis du mit dem Zeitreihen-Vektor anfängst - mit dem Konvertierungs-Konstruktor kann es dir nämlich ganz schnell passieren, daß du verschiedene Vektortypen bunt durcheinander verrechnest.
-
ein hinweis auf http://www.boost.org/libs/utility/operators.htm ist hier angebracht.
eine möglichkeit, ein duplikat der vector-implementation zu vermeiden, besteht darin, diese als vector basisklasse nach dem typ des vectors zu parametrisieren:
template<typename T, template<typename> class U> class VectorBase { private: T* vectorPtr; COUNTER dim; protected: VectorBase(COUNTER n, const T& initVal); ~VectorBase(){delete [] vectorPtr;} VectorBase(const VectorBase&); VectorBase& operator=(const VectorBase&); public: U<T>& operator+=(const VectorBase&); // hier wird dann ein return static_cast< U<T>& >( *this ) benötigt U<T>& operator-=(const VectorBase&); // usw }; template<typename T, template<typename> class U> U<T> operator+(U<T> lhs, const U<T>& rhs) { return lhs += rhs; } // usw template<typename T> class Vector : public VectorBase< T, Vector > { // ... }; template<typename T> class NumericVector : public VectorBase< T, NumericVector > { // ... };Vector und NumericVector sind jetzt zwei template klassen, die nichts miteinander zu tun haben, die gemeinsame representation der daten ist dennoch nur einmal implementiert. das ist auch keine perfekte lösung...