einfache Matrizenklasse 2. Versuch



  • Hoi, hier nun meine kompakte und korrigierte(?) Version einer Matrizenklasse, die nur das Ziel hat, einen Reihenvektor(3) (hier ein Point) mit anderen 3*3 Matrizen zu multiplizieren.

    using floatVec = std::vector<float>;
    
    class Matrix
    {
    public:
    	Matrix() {}
    
    	Matrix(const std::size_t rows, const std::size_t columns);
    	Matrix(const std::size_t rows, const std::size_t columns, const floatVec& vec);
    	
    	float element(const std::size_t row, const std::size_t column) const;
    	floatVec elements() const;
    	std::size_t rows() const;
    	std::size_t columns() const;
    	std::size_t size() const;
    	void print(const std::string& note = {}, std::ostream& stream = std::cout) const;
    
    	void operator+=(const float f);
    	void operator-=(const float f);
    	void operator*=(const float f);
    	void operator/=(const float f);
    	void operator+=(const Matrix& mx);
    	void operator-=(const Matrix& mx);
    	Matrix operator+(const float f) const;
    	Matrix operator-(const float f) const;
    	Matrix operator*(const float f) const;
    	Matrix operator/(const float f) const;
    	Matrix operator+(const Matrix& mx) const;
    	Matrix operator-(const Matrix& mx) const;
    	Matrix operator*(const Matrix& mx) const;
    	
    protected:
    	void fillMatrix(const float v); 
    	void setElement(const std::size_t row, const std::size_t column, const float v);
    	void copyMatrix(const Matrix& mx);
    	bool sameDimension(const Matrix& mx) const;
    
    private:
    	std::size_t rows_ = 0; 
    	std::size_t columns_ = 0;
    	floatVec elements_;
    
    	void resizeElements(const float v);
    	void printElement(const std::size_t row, const std::size_t column, std::ostream& stream = std::cout) const;
    };
    
    class UnitMat : public Matrix
    {
    public:
    	UnitMat() : Matrix(3, 3) { construct(3); } 
    
    	UnitMat(const std::size_t size) :                 Matrix(size, size) { construct(size); }
    	UnitMat(const std::size_t size, const float dv) : Matrix(size, size) { construct(size); }
    
    private:
    	void construct(const std::size_t size)
    	{
    		for (std::size_t n = 0; n < size; ++n) {
    			setElement(n, n, 1.);
    		}
    	}
    };
    
    
    class MXMat : public UnitMat
    {
    public:
    	MXMat() : UnitMat(3)
    	{
    		setElement(0, 0, 1.);
    		setElement(1, 1, -1.);
    	}
    };
    
    class MYMat : public UnitMat
    {
    public:
    	MYMat() : UnitMat(3)
    	{
    		setElement(0, 0, -1.);
    		setElement(1, 1, 1.);
    	}
    };
    
    class ScaleMat : public UnitMat
    {
    public:
    	ScaleMat() : UnitMat(3) {}
    
    	ScaleMat(const float x, const float y) : UnitMat(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 UnitMat
    {
    public:
    	TransMat() : UnitMat(3) {}
    
    	TransMat(const float x, const float y) : UnitMat(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);
    	}
    
    	TransMat invers() const
    	{
    		TransMat tm(-tx, -ty);
    		return tm;
    	}
    
    	float x() const { return tx; }
    	float y() const { return ty; }
    
    private:
    	float tx = 0.;
    	float ty = 0.;
    };
    
    class RotRMat : public UnitMat
    {
    public:
    	RotRMat() : UnitMat(3) {}
    
    	RotRMat(const int angle) : UnitMat(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 UnitMat
    {
    public:
    	RotLMat() : UnitMat(3) {}
    
    	RotLMat(const int angle) : UnitMat(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;
    };
    

    und die passende Anwendung dafür

    class Point : public Matrix
    {
    public:
    	Point() {}
    	Point(const Matrix& p) : Matrix(p) { copyPoint(p); }
    
    	Point(const float x, const float y) : Matrix(1, 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) : Matrix(1, 3, vec_), 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) : Matrix(1, 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);
    		}
    	}
    	
    	float x = 0.;
    	float y = 0.;
    	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& note = {}, std::ostream& stream = std::cout) const
    	{
    		stream << '\n';
    		if (!note.empty())
    			stream << note << " ";
    
    		const auto p = 3;
    		const auto w = p + 5;
    		stream << " x: ";
    		stream << std::setw(w) << std::fixed << std::setprecision(p) << x;
    		stream << "   y: ";
    		stream << std::setw(w) << std::fixed << std::setprecision(p) << y;
    	}
    
    private:
    	std::size_t dim_ = 0;
    	floatVec vec_;	
    	void copyPoint(const Matrix& mx)
    	{
    		copyMatrix(mx);
    		if (mx.columns() >= 3)
    		{
    			x = mx.element(0, 0);
    			y = mx.element(0, 1);
    			setElement(0, 0, x);
    			setElement(0, 1, y);
    			setElement(0, 2, 1.);
    		}
    	}
    };
    

    Das alles läuft soweit korrekt, nur statt

    MXMat mX;
    Point p(3, 4);
    p = p * mX;
    

    kann man nicht schreiben

    MXMat mX;
    Point p(3, 4);
    p *= mX;
    

    Irgendwelche Kommentare sind wie immer willkommen 😉



  • Hm, ich sehe Vererbung ohne virtuellen Destruktor einen leeren default Konstruktor und Copy- ohne move- und assignment Konstruktor. Das könntest du noch eine Runde nachbessern.

    Wenn eine Ausgabe geplant würde ich eine Überladung von "<<" erwarten.

    Ich hätte, wenn ich eine Matrix Klasse selber schreiben würde, dass wahrscheinlich templatesiert, damit ich immer mit genau dem Typ rechne, den ich haben will.



  • Beim ersten Drübergucken:

    • element sollte keine Kopie zurückgeben, sondern eine Referenz auf einen const float. Mag bei float nicht so schlimm sein, aber wenn du Schlangenmenschs Vorschlag berücksichtigst und aus der Klasse ein template machst kannst du im Vorfeld nicht wissen, wie teuer ein Kopie eines Elements wird.
    • alle binären Operatoren sollten freie Funktionen sein
    • warum ist die Einheitsmatrix eine eigene Klasse? Das ist vom Typ her genau das gleiche wie eine normale Matrix, nur dass die Elemente bestimmte Werte haben. Das würde ich über eine freie Funktion realisieren, die eine Matrix der entsprechenden Größe erzeugt und die Elemente initialisiert. Das Gleiche gilt für deine anderen Matrixtypen. Das ist in etwa so, als würdest du eine Basisklasse "Auto" bauen und rote, grüne und blaue Autos davon ableiten.
    • print sollte kein Member sein, sondern eine freie Funktion
    • du könntest den float & operator( int,int ) (+const Variante)für den Elementzugriff implementieren, dann haste die Möglichkeit so
    Matrix m( 3,3 );
    m( 0,0 ) = 1.0f;
    

    auf die Elemente der Matrix zuzugreifen



  • Mir sind das zu viele Klassen!

    Wenn ich gleichzeitig rotieren und skalieren will, brauche ich dann eine weitere neue Klasse? Mir scheint, dass du eigentlich eher Funktionen brauchst, die dir bestimmte Matrizen erzeugen, wie auch @SeppJ schon sagte.

    @zeropage sagte in einfache Matrizenklasse 2. Versuch:

    class Point : public Matrix

    Hier hast du mich wirklich verloren. Ein Punkt ist eine Matrix?

    Und noch viele andere Dinge, UnitMat hat 3 Konstruktoren. Sind die wirklich alle sinnvoll? Insbesondere, wenn der Parameter dv überhaupt nicht verwendet wird? Du möchtest da diagonalValues haben. Kannst du auch per Default-Parameter lösen. Aber die gesamte Klasse - warum ist das bei dir eine eigene, von Matrix erbende Klasse? Was gewinnst du dadurch? Die besonderen Eigenschaften werden nirgends ausgenutzt, z.B. indem man nur die Diagonalelemente speichert (ich schrieb das schon zuvor).

    Warum soll man bei dir eine Matrix mit dem Default-Konstruktor erstellen können?

    Warum gibt es kein virtual ~Matrix() = default;? Willst du die Klassen überhaupt virtuell verwenden?



  • @wob sagte in einfache Matrizenklasse 2. Versuch:

    Insbesondere, wenn der Parameter dv überhaupt nicht verwendet wird?

    Was für ein verdammter Parameter?



  • @zeropage sagte in einfache Matrizenklasse 2. Versuch:

    @wob sagte in einfache Matrizenklasse 2. Versuch:

    Insbesondere, wenn der Parameter dv überhaupt nicht verwendet wird?

    Was für ein verdammter Parameter?

    Der hier aus deiner Klasse UnitMat:

    class UnitMat : public Matrix
    {
    public:
    	UnitMat() : Matrix(3, 3) { construct(3); } 
    
    	UnitMat(const std::size_t size) :                 Matrix(size, size) { construct(size); }
            // **************************** guckst du hier ***
            // **************************** ↓↓↓↓↓↓↓↓↓↓↓↓↓↓ ***
    	UnitMat(const std::size_t size, const float dv) : Matrix(size, size) { construct(size); }
    


  • Öh, tatsache. Da ist der Konstruktor noch übrig geblieben. Zum Glück wird der aber nicht mehr verwendet, trotzdem schon ulkig.

    Wieso ist Point : public Matrix eigenartig? Der Point ist in diesem Fall doch eine Matrix(1, 3)?


  • Mod

    @zeropage sagte in einfache Matrizenklasse 2. Versuch:

    Wieso ist Point : public Matrix eigenartig? Der Point ist in diesem Fall doch eine Matrix(1, 3)?

    Nein, ein Vektor wäre eine Sonderform einer Matrix, ein Punkt nicht.

    PS: Das ist dann auch eine sinnvolle Anwendung von Vererbung, da das Konzept eines euklidischen Vektors alle Eigenschaften einer 1xN-Matrix hat, man aber üblicherweise noch einige zusätzliche Eigenschaften und Operationen für diese definiert. Wäre hier aber dennoch etwas unhandlich, weil dann komische Sachen bei Nutzung von Matrizen auftauchen, wenn diese sich plötzlich spontan in Vektoren verwandeln können.

    Zu den anderen Arten, wie du Vererbung benutzt, wurde im letzten und diesen Thread schon genug gesagt.



  • Und das das Ding genauso läuft, wofür es geschrieben wurde, zählt gar nicht?

    Oder habe ich da irgendwas drin, analog zu system("PAUSE")?



  • @Swordfish sagte in einfache Matrizenklasse, Vererbung:

    @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!



  • @zeropage sagte in einfache Matrizenklasse 2. Versuch:

    Und das das Ding genauso läuft, wofür es geschrieben wurde, zählt gar nicht?

    Nur bedingt. In komplexeren Projekten fällt dir sowas sehr schnell auf die Füße, also warum falsch angewöhnen?



  • @zeropage sagte in einfache Matrizenklasse 2. Versuch:

    Und das das Ding genauso läuft, wofür es geschrieben wurde, zählt gar nicht?

    Oder habe ich da irgendwas drin, analog zu system("PAUSE")?

    Es funktioniert aber halt leider nicht alles, was man damit machen kann, so wie du es dir vorstellst. Wenn du zum Beispiel einen std::vector<Matrix*> hast und da unterschiedliche abgeleitete Matrix Typen drin hast, wie zerstörst du die dann? Soweit ich weiß ist das dann sogar undefined behavior und damit schlimmer als system("PAUSE").


Log in to reply