einfache Matrizenklasse, Vererbung


  • Mod

    @zeropage sagte in einfache Matrizenklasse, Vererbung:

    Nur mit der Vererbung habe ich Probleme.

    Nein. Das meinst du vielleicht, aber du hast noch viel mehr Probleme. Dein Code hat momentan viel zu viele Baustellen. Außerdem habe ich Zweifel, ob hier Vererbung überhaupt eine sinnvolle Abstraktion ist. Haben Dmat, RVek, CVek oder was immer du da alles ableiten möchtest, irgendwelche relevanten Eigenschaften? Das einzige, was sie bieten, sind spezielle Konstruktoren. Das sind keine Spezialklassen, sondern nur besondere Initialisierungen. Die kannst du entweder direkt in der Matrixklasse anbieten oder als Hilfsfunktionen, die entsprechende Objekte bauen, z.B. eine Funktion für Einheitsmatrizen der Größe N, die dir einfach nur den Matrixkonstruktor passend aufruft. Faustregel: Wenn eine Klasse keine Membervariablen hat, dann ist es keine sinnvoll definierte Klasse.

    Empfehlung: Nimm ganz weit Abstand und implementier erst einmal nur die Matrix-Klasse. Keine Spezialfälle, keine Vererbung, dafür aber vollständig, richtig, und gründlich getestet. Dann merkst du, was du überhaupt wirklich brauchst und was Planungsrelikte sind. Wenn damit zufrieden bist, fragst du uns noch einmal nach unserer Meinung zu der vollständigen Klasse.

    Und danach kannst du dir dann Spezialfälle vornehmen, aber ich sage voraus, dass die Basisklasse die meisten schon erschlagen wird.



  • Ich glaube, ich verstehe dein gesamtes Vorgehen nicht. Ich stelle mir die Frage, was das alles soll.

    Also zum Beispiel, dass Mat von Matrix erbt. Was unterscheidet die beiden? Ist Vererbung hier überhaupt sinnvoll - sollten nicht die Operationen von Mat eher in Matrix sein?

    Warum eine spezielle Klasse DMat (ich nehme mal an, dass soll für Diagonalmatrix stehen?) - die aber bei dir von Mat erbt?! Das Besondere an einer Diagonalmatrix ist doch, dass alles 0 ist außer der Diagonalen. Du brauchst also, wenn du eine Matrix der Größe n×nn \times n hast, die diagonal ist, nur nn Werte zu speichern. Der Rest ist 0. Das erlaubt sehr viele "Shortscuts" bei Berechnungen. Das spricht absolut dagegen, von Matrix zu erben. Warum gibt es die Member-Variable dv_? Und was passiert mit ihr, wenn du ein Element der Diagonalen auf was anderes setzt?

    Generell scheinst du extrem viele Funktionen zu haben - konzentriere dich lieber darauf, die wirklich wichtigen Funktionen zu implementieren.

    Was sind CVec und RVec?

    Fragen über Fragen...

    Zur Namensgebung, dass Array bei dir nicht Array, sondern 2d-Array bedeutet, hatte ich ja schon was gesagt.



  • RVec und CVec sollen Zeilen- und Spaltenvektoren sein
    Aber ok. Mit irgendwas muss ich ja anfangen. Wenn Vererbung hier nicht passt, habe ich sonst keine Anwendung, in der das passen würde.
    Und wenn doch was kommen würde, würde ich immer noch nicht wissen, was man da tun muss.



  • @SeppJ sagte in einfache Matrizenklasse, Vererbung:

    Empfehlung: Nimm ganz weit Abstand und implementier erst einmal nur die Matrix-Klasse. Keine Spezialfälle, keine Vererbung, dafür aber vollständig, richtig, und gründlich getestet. Dann merkst du, was du überhaupt wirklich brauchst und was Planungsrelikte sind. Wenn damit zufrieden bist, fragst du uns noch einmal nach unserer Meinung zu der vollständigen Klasse.

    So betrachtet ist das eigentlich in Ordnung. Ich muss ja keine Vererbung haben, ich wollte nur. Aber stimmt, Lebenszeit ist kostbar. Sollte man nicht rumtrödeln.



  • PS: (ich antworte gerade immer zu schnell) Ich habe noch andere Matrizen, zB `Point', deshalb habe ich mir das doch alles geschrieben.

    class Point : public RVec
    {
    public:
    	Point() {}
    	~Point() {}
    	Point(const Mat& p) : RVec(p) {}
    	Point(Mat& p) : RVec(p) {}
    	Point operator=(const Point& p) {}
    
    	Point(const float x, const float y) : RVec(3, { x, y }), x(x), y(y), dim_(2) 
    	{ 
    		fillMatrix(1.);
    		setElement(0, 0, x);
    		setElement(0, 1, y);
    	}
    	Point(std::initializer_list<float> list) : RVec(3, list), vec_(list), dim_(list.size())
    	{
    		fillMatrix(1.);
    		if (dim_ >= 2)
    		{
    			x = vec_[0];
    			y = vec_[1];
    			setElement(0, 0, x);
    			setElement(0, 1, y);
    		}
    	}
    	Point(const std::vector<float>& vec) : RVec(3, vec_), vec_(vec), dim_(vec.size())
    	{
    		fillMatrix(1.);
    		if (dim_ >= 2)
    		{
    			x = vec_[0];
    			y = vec_[1];
    			setElement(0, 0, x);
    			setElement(0, 1, y);
    		}
    	}
    	
    	int cx() const { return Math::toInt(x); }
    	int cy() const { return Math::toInt(y); }
    	std::size_t dim() const { return dim_; }
    
    	void printPoint(const std::string& name = {}, std::ostream& stream = std::cout) const
    	{
    		stream << '\n';
    		if (!name.empty())
    			stream << name << " ";
    
    		const auto np = 3;
    		const auto n = np + 5;
    		stream << " x: ";
    		stream << std::setw(n) << std::fixed << std::setprecision(np) << x;
    		stream << "   y: ";
    		stream << std::setw(n) << std::fixed << std::setprecision(np) << y;
    	}
    
    	float x = 0.;
    	float y = 0.;
    
    private:
    	std::size_t dim_ = 0;
    	floatVec vec_;	
    };
    
    
    
    class MXMat : public DMat
    {
    public:
    	~MXMat() {}
    	MXMat(const DMat& mt) : DMat(mt) {}
    	MXMat operator=(const MXMat& mt) { copyMatrix(mt); }
    
    	MXMat() : DMat(3)
    	{
    		setElement(0, 0, 1.);
    		setElement(1, 1, -1.);
    	}
    };
    
    class MYMat : public DMat
    {
    public:
    	~MYMat() {}
    	MYMat(const DMat& mt) : DMat(mt) {}
    	MYMat operator=(const MYMat& mt) { copyMatrix(mt); }
    
    	MYMat() : DMat(3)
    	{
    		setElement(0, 0, -1.);
    		setElement(1, 1, 1.);
    	}
    };
    
    class ScaleMat : public DMat
    {
    public:
    	ScaleMat() {}
    	~ScaleMat() {}
    	ScaleMat(const DMat& mt) : DMat(mt) {}
    	ScaleMat operator=(const ScaleMat& mt) { copyMatrix(mt); }
    
    	ScaleMat(const float x, const float y) : DMat(3), sx(x), sy(y)
    	{
    		setElement(0, 0, sx);
    		setElement(1, 1, sy);
    	}
    
    	void setScale(const float x, const float y)
    	{
    		sx = x;
    		sy = y;
    		setElement(0, 0, sx);
    		setElement(1, 1, sy);
    	}
    
    	float x() const { return sx; }
    	float y() const { return sy; }
    
    private:
    	float sx = 0.;
    	float sy = 0.;
    };
    
    class TransMat : public DMat
    {
    public:
    	TransMat() {}
    	~TransMat() {}
    	TransMat(const DMat& mt) : DMat(mt) {}
    	TransMat operator=(const TransMat& mt) { copyMatrix(mt); }
    
    	TransMat(const float x, const float y) : DMat(3), tx(x), ty(y)
    	{
    		setElement(2, 0, tx);
    		setElement(2, 1, ty);
    	}
    
    	void setTrans(const float x, const float y)
    	{
    		tx = x;
    		ty = y;
    		setElement(2, 0, tx);
    		setElement(2, 1, ty);
    	}
    
    	float x() const{return tx;}
    	float y() const{return ty;}
    
    private:
    	float tx = 0.;
    	float ty = 0.;
    };
    
    class RotRMat : public DMat
    {
    public:	
    	RotRMat() {}
    	~RotRMat() {}
    	RotRMat(const DMat& mt) : DMat(mt) {}
    	RotRMat operator=(const DMat& mt) { copyMatrix(mt); }
    
    	RotRMat(const int angle) : DMat(3), phi_(angle)
    	{
    		const float sin = std::sin(Math::toRad(phi_));
    		const float cos = std::cos(Math::toRad(phi_));
    		setElement(0, 0, cos);
    		setElement(0, 1, sin);
    		setElement(1, 0, -sin);
    		setElement(1, 1, cos);
    	}
    
    	void setAngle(const int angle)
    	{
    		phi_ = angle;
    		const float sin = std::sin(Math::toRad(phi_));
    		const float cos = std::cos(Math::toRad(phi_));
    		setElement(0, 0, cos);
    		setElement(0, 1, sin);
    		setElement(1, 0, -sin);
    		setElement(1, 1, cos);
    	}
    
    	int phi() const{return phi_;}
    
    private:
    	int phi_ = 0;
    };
    
    class RotLMat : public DMat
    {
    public:
    	RotLMat() {}
    	~RotLMat() {}
    	RotLMat(const RotLMat& mt) : DMat(mt) {}
    	RotLMat operator=(const RotLMat& mt) { copyMatrix(mt); }
    
    	RotLMat(const int angle) : DMat(3), phi_(angle)
    	{
    		const float sin = std::sin(Math::toRad(phi_));
    		const float cos = std::cos(Math::toRad(phi_));
    		setElement(0, 0, cos);
    		setElement(0, 1, -sin);
    		setElement(1, 0, sin);
    		setElement(1, 1, cos);
    	}
    
    	void setAngle(const int angle)
    	{
    		phi_ = angle;
    		const float sin = std::sin(Math::toRad(phi_));
    		const float cos = std::cos(Math::toRad(phi_));
    		setElement(0, 0, cos);
    		setElement(0, 1, -sin);
    		setElement(1, 0, sin);
    		setElement(1, 1, cos);
    	}
    
    	int phi() const{return phi_;}
    
    private:
    	int phi_ = 0;
    };
    
    

    Ich wollte nicht Mat point(2, 3); haben, weil meine anderen Klassen denPointhaben wollen. Aber gut, ich werde erst mal eine kompakte Matrix-Klasse schreiben und dann sehen, wie ich die einbinden kann. Habe fertig.



  • @SeppJ sagte in einfache Matrizenklasse, Vererbung:

    Haben Dmat, RVek, CVek oder was immer du da alles ableiten möchtest, irgendwelche relevanten Eigenschaften?

    Dmat könnte nur die Diagonalelemente effizient speichern, aber dennoch ein Matrix-Interface anbieten, das für Elemente außerhalb der Diagonalen einfach nur 0 zurückgibt. In dem Fall sollte man aber nicht von dieser Matrix erben, sondern stattdessen (wenn man es wirklich braucht):

    • (OOP) Matrix und DMat von einer Matrix-Interface-Basisklasse ableiten, bei der die relevanten Matrix-Funktionen pure virtual sind.
    • (Konzeptuell) Dokumentieren, wie ein Matrix-Interface aussieht (vgl. C++ Named Requirements, std::string/std::string_view, etc.) und davon ausgehen, dass ein Typ die relevanten Operationen unterstützt, oder das ab C++20 mit Concepts unterfüttern.

    Rvek und Cvek sind in der Tat (auch algebraisch) 1xN und Nx1-Matrizen und könnten durchaus von Matrix erben, da sie eventuell auch noch zusätzliche Funktionen wie z.B. length() oder aber Accessoren wie einen nur einstelligen operator()(std::size_t i) anbieten könnten. Hier würde ich aber nicht unbedingt zwei verschiedene Klassen anbieten. Das ist redundant und auch später in der Anwendung eher verwirrend. Stattdessen vielleicht festlegen, dass Vector immer ein Spaltenvektor ist (Matrix-Vektor-Multiplikation finde ich aus Gewohnheit als AxA\bold{x} etwas intuitiver als xA\bold{x}A).

    Empfehlung: Nimm ganz weit Abstand und implementier erst einmal nur die Matrix-Klasse. Keine Spezialfälle, keine Vererbung, dafür aber vollständig, richtig, und gründlich getestet. Dann merkst du, was du überhaupt wirklich brauchst und was Planungsrelikte sind. Wenn damit zufrieden bist, fragst du uns noch einmal nach unserer Meinung zu der vollständigen Klasse.

    Genau das möchte ich hier auch unterschreiben und dick unterstreichen! 😉



  • @zeropage sagte in einfache Matrizenklasse, Vererbung:

    Ich wollte nicht Mat point(2, 3); haben, weil meine anderen Klassen denPointhaben wollen. Aber gut, ich werde erst mal eine kompakte Matrix-Klasse schreiben und dann sehen, wie ich die einbinden kann. Habe fertig.

    Das ist ja echt okay für Vector oder Point einen eigenen Typen haben zu wollen. Aber wie gesagt, mach lieber erstmal nur die Matrix mit allem was dazu gehört (Operatoren und so) und ignorier alles andere. Wenn die dann in deinen Augen perfekt ist, dann bau darauf andere Klassen wie Vector oder Point auf. Ansonsten verhaspelt man sich schnell und verliert den Faden (bzw. die klare Linie) und produziert Chaoscode - so ginge mir das zumindest 😉

    Und nochwas: Wenn du die Matrix und Vektoren nur für grafisch-geometrische Berechnungen brauchst und deren Größen immer nur recht klein sind (3x3, 4x4, 2x1, etc) und bereits zur Compile-Zeit feststehen, bietet es sich eventuell an die Matrix mit fixer Größe zu implementieren. Also z.B. als Matrix<float, 3, 3>, analog zu std::array<T, N>. Das wäre jedenfalls effizienter, wenn du hauptsächlich Koordinaten herumreichst und die zwischendurch mal mit einer Matrix transformierst. Ein std::vector muss schliesslich immer dynamischen Speicher reservieren und ein Elementzugriff erfordert eine Indirektion in Speicher der irgendwo im RAM liegen kann. Matrizen fester Größe können dagegen auf dem Stack liegen (Speicher-Resverierung de facto kostenlos und an cache-freundlicher Speicheradresse, da "nah bei der Action").

    Dynamische Matrizen sind eher was wenn du z.B. riesige Gleichungssysteme lösen willst oder ähnliches.



  • Ok. Danke nochmals.

    Meine Matrizenklasse war ja eigentlich schon fertig, lief wie gewünscht für meine kleine grafische Anwendung. Sogar der Point und die Transformations-Matrizen konnten korrekt erben bzw arbeiten.

    Nur beim ins Saubere schreiben bin ich dann auf den Trichter gekommen, eine Klasse für die Matrix und eine Klasse für die Operatoren zu schreiben. Das ging nach hinten los.

    Ich werde also meine alte Version etwas an die hiesigen Ratschläge anpassen, sonst werde ich nicht viel ändern müssen. Alles darüber scheint schief zu gehen.

    Und auch der theoretische Kram sollte mich nicht interessieren. Hauptsache, es gibt eine 3*3 Matrix mit den benötigten Werten. Und einen Vektor, also eine Mat(1 * 3), mit dem ich die multiplizieren kann.

    Sonst alles gut. Grüße aus der verrottenden Hauptstadt 🙂



  • @zeropage sagte in einfache Matrizenklasse, Vererbung:

    Und auch der theoretische Kram sollte mich nicht interessieren. Hauptsache,

    Finnegans Post sollte Dich in Hinblick auf gutes Design sehr wohl interessieren!



  • Mit irgendwelchen Designs falle ich hier nur auf die Schnauze. Hätte ich meine alte Version nicht geändert, hätte SeppJ keinen Vortrag halten müssen. Weil die war genau das, was er empfohlen hatte.


Anmelden zum Antworten