Was macht man mit solch einer Fehlermeldung?



  • Was @SeppJ sagen will:
    Es gibt jede Menge Alternativen zur Herausgabe von privaten Membern. Die schlimmste Alternative wäre, die Member public zu machen, die beste wäre "setter" und "getter" zu machen. Sage ich jetzt mal so, ohne die Funktion der Klasse zu kennen.



  • @zeropage sagte in Was macht man mit solch einer Fehlermeldung?:

    @wob Ich werde natürlich versuchen, die Klasse ein wenig einzudampfen, wenn das soviel Anstoss erregt. Aber nur soweit, wie es mir nützt. Wenn es mir nichts bringt, mache ich es nicht.

    Die siehst das falsch. Es geht hier nicht darum ob dein Quelltext "Anstoß" erregt. Das ist uns letztendlich egal. Aber du bekommst hier quasi von allen Seiten eigentlich die gleiche Kritik. In vielen Themen kann man bei C++ durchaus diskutieren, aber gerade bei deinem Thema herrscht relative Einigkeit. Ich kann dir nur raten, dir die Vorschläge zu Herzen zu nehmen. Schließlich willst du ja auch dazu lernen.



  • @SeppJ sagte in Was macht man mit solch einer Fehlermeldung?:

    Ich weiß nicht ganz, worauf du dich hier mit dem operator() beziehen möchtest.

    Gemeint ist, das mit solchen Operatoren auch ein privater Member nach außen geholt wird und als Referenz auch überschrieben wird.


  • Mod

    @zeropage sagte in Was macht man mit solch einer Fehlermeldung?:

    Ich werde natürlich versuchen, die Klasse ein wenig einzudampfen, wenn das soviel Anstoss erregt. Aber nur soweit, wie es mir nützt. Wenn es mir nichts bringt, mache ich es nicht.

    Wir stoßen uns nicht an, und wir wollen dich auch nicht fertig machen. Natürlich bezweifelt niemand, dass du eine Klasse mit vier Membern voll im Griff hast. Aber ich will dir zeigen, wo theoretische Probleme mit deiner Modellierung liegen, selbst wenn sie praktisch bei einem derart einfachen Problem keine Rolle spielen. Denn wo, wenn nicht an solch einfachen Fragestellungen, kann man die Designprobleme verstehen und üben? Denn wenn du eine komplizierte Fragestellung hast, dann bist du hauptsächlich mit der Schwierigkeit der Fragestellung beschäftigt. Wenn du dann gutes Design nicht gewöhnt bist, wirst du die schwierige Fragestellung aus Unerfahrenheit mit einem schlechten Design angehen. Und das hast du dann nicht mehr im Griff.



  • Ganz ehrlich, ich glaube nicht daran, das ich jemals wirklich komplizierte Sachen lösen werde. Dafür fehlt mir eigentlich alles. Es gibt Künstler, die machen Ausstellungen, verdienen Geld, erfinden eine neue Kunstform und es gibt Leute, die pinseln im Privaten ein wenig auf Papier rum. Ich gehöre zu letzteren.



  • Was ich mich jetzt allerdings frage, ob die viele Kritik nur zu diesem Beispiel vorliegt, oder ob generell meine ganze Vorstellung von Code schreiben falsch ist.
    Vielleicht sollt ich einige übersichtliche Klassen von mir zeigen, ob da ein grundsätzliches Problem vorliegt. Denn, auch wenn es nur im Privaten ist, eingermaßen korrekt sollte es schon ein.



  • @wob sagte in Was macht man mit solch einer Fehlermeldung?:

    Ich persönlich tue mich bei so reinen einfachen Datenobjekten immer schwer damit, den Vorteil einer "Full-Grown"-Klasse zu sehen gegenüber

    template<typename T>
    struct Rect {
        T top, left, right, bottom;
    };
    

    Manchmal ist simpler besser. Aber das ist ja eine ganz andere Diskussion.

    Wenn du jetzt noch die Reihenfolge von top und left vertauscht, dann ... 🙂


  • Mod

    @zeropage sagte in Was macht man mit solch einer Fehlermeldung?:

    Was ich mich jetzt allerdings frage, ob die viele Kritik nur zu diesem Beispiel vorliegt, oder ob generell meine ganze Vorstellung von Code schreiben falsch ist.

    Nee, ist schon ganz ok so. Damit kannst du gut zurecht kommen für deine eigenen Programme



  • @zeropage sagte in Was macht man mit solch einer Fehlermeldung?:

    Was ich mich jetzt allerdings frage, ob die viele Kritik nur zu diesem Beispiel vorliegt, oder ob generell meine ganze Vorstellung von Code schreiben falsch ist.
    Vielleicht sollt ich einige übersichtliche Klassen von mir zeigen, ob da ein grundsätzliches Problem vorliegt. Denn, auch wenn es nur im Privaten ist, eingermaßen korrekt sollte es schon ein.

    Die Einstellung gefällt mir schon besser. Du kannst gerne eine paar Klassen-Definitionen posten.
    Du hast ja die Grundlagen drauf, soweit ich sehen kann. Im Grunde müssen bei nur noch ein paar Ecken und Kanten geschliffen werden.
    Was @hustbaer , @SeppJ , @Bashar und andere alte Hasen dir vorschlagen, hat in aller Regel Hand und Fuß. Es macht also Sinn, zumindest mal drüber nachzudenken.

    Hier ein paar Grundregeln ( Checkliste ) beim Design von Klassen:
    https://www.cs.odu.edu/~zeil/cs333/latest/Public/checklist/index.html



  • Ich musste die Klasse Rectein wenig ändern, damit sie auch tatsächlich bei mir passt und ich denke, dadurch fallen einige Kritik-Punkte weg.
    Dazu eine Bemerkung. Ich war ja etwas konfus, über die Bemerkung von SeppJ "konfus". Inzwischen meine ich das zu verstehen. Als Leser hatte man den Eindruck, die ganze Geschichte sei etwas konfus. Und das war sie ja auch, ich war ziemlich verhuscht während dieser Klasse, sonst wären es 1.) leine const-Member geworden und 2.) ist es mir 2 Tage nicht aufgefallen und ich habe wie blöd nach dem Fehler gesucht. Das muss schon etwas sonderbar wirken. Kann aber nicht garantieren, das es jetzt klarer auschaut.

    Die aktuelle Klasse Rect

    template<typename T>
    class Rect
    {
    	using val_t = T;
    public:
    	Rect() = default;
    	Rect( const val_t x0, const val_t y0, const val_t x, const val_t y) 
    		: points_ { Point2D<val_t>(x0, y0), Point2D<val_t>(x, y0) , Point2D<val_t>(x, y) , Point2D<val_t>(x0, y) } {}
    
    	std::vector<Point2D<val_t>> points() const { return points_; } //temporär drin, noch nicht sicher ob ich es auch benötige
    
    	val_t left() const { return points_[0].x(); }
    	val_t bottom() const { return points_[0].y(); }
    	val_t right() const { return points_[2].x(); }
    	val_t top() const { return points_[2].y(); }
    
    private:
    	std::vector<Point2D<val_t>> points_;
    };
    

    Und weil es Spekulationen über den Typ Point2Dgab, hier die entsprechende Klasse. Die betrachte ich als abgeschlossen.

    template<typename T>
    class Point2D
    {
    	using val_t = T;
    	using size_t = std::size_t;
    public:
    	Point2D() = default;
    	Point2D(const val_t x, const val_t y)
    		: point{ Mat::Point2D<val_t>(x, y) } {}
    
    	Point2D(const Mat_T<val_t>& mT)
    		: point{ mT } {}
    
    	Point2D(const std::vector<val_t>& pVec)
    	{
    		if (pVec.size() != 2)
    			throw std::out_of_range("Point2D(vector): vector.size(): "
    				+ std::to_string(pVec.size()));
    
    		point(0, 0) = pVec[0];
    		point(0, 1) = pVec[1];
    		point(0, 2) = 1;
    	}
    
    	Point2D<val_t> operator*(const Mat_T<val_t>& mT) const
    	{
    		Point2D<val_t> p = (*this);
    		multiply(p, mT);
    		return p;
    	}
    
    	val_t operator()(const size_t row, const size_t column) const
    	{
    		if (row > 1 || column > 2)
    			throw std::out_of_range("Point2D::operator(): row: "
    				+ std::to_string(row) + " column: "
    				+ std::to_string(column));
    
    		return point(row, column);
    	}
    
    	val_t& operator()(const size_t row, const size_t column)
    	{
    		if (row > 1 || column > 2)
    			throw std::out_of_range("Point2D::operator(): row: "
    				+ std::to_string(row) + " column: "
    				+ std::to_string(column));
    
    		return point(row, column);
    	}
    
    	val_t x() const { return (*this)(0, 0); }
    	val_t y() const { return (*this)(0, 1); }
    	val_t& x() { return (*this)(0, 0); }
    	val_t& y() { return (*this)(0, 1); }
    
    	void print(const int cwidth = 3, std::ostream& stream = std::cout)
    	{
    		const int cp = 4;
    		const int cpwidth = cwidth + cp;
    		stream << "\nx:" << std::setw(cpwidth) << std::fixed << std::setprecision(cp) << x();
    		stream << "  y:" << std::setw(cpwidth) << std::fixed << std::setprecision(cp) << y();
    	}
    
    private:
    	Mat_T<val_t> point = Mat::Point2D<val_t>(0, 0); //eine 1x3 Matrix, zu verfolgen unter 'Entwicklung einer einfachen Matrizenklasse'
    
    	void multiply(Point2D& p, const Mat_T<val_t>& mT) const
    	{
    		Mat_T<val_t> pMat = Mat::Point2D<val_t>(p(0, 0), p(0, 1)) * mT;
    		p = pMat;
    	}
    };
    


  • @zeropage sagte in Was macht man mit solch einer Fehlermeldung?:

    Rect() = default;

    Was für ein Rechteck mit ohne Ecken mag das wohl sein.



  • Gut, auch wenn ich den Scherz nicht zur Gänze verstanden habe, dieser Default-Konstruktor scheint zumindest tatsächlich überflüssig zu sein.



  • Wieso denn ein vector? Das scheint mir nicht sinnvoll zu sein.



  • Hm, was wäre denn besser? Ist ja nocht temporär.

    Wenn ich das Rect tranformieren will, benötige ich doch die Points?


  • Gesperrt

    width und height finde ich bei Rechtecken wesentlich als Rückgabewert von Methoden. Usw.

    Überelegenswert fände ich auch, ob per Template Argument z.B. festgelegt werden können soll, ob das Koordinatensystem z.B. nach y Richtung top oder Richtung bottom geht 🌝

    Edit: Ausser es wird nicht gebraucht. Aber sonst ist ja die Wiederverwendbarkeit einer Funktion ein Zweck seiner ihrer Existenz. Das vermindert die Schreibmühen. Anstatt die gesamte Berechnung jeweils hinzuschreiben, ist es dann möglich einfach z.B. Width() zu schreiben und sich nicht nochmals um das Tippen und Denken zu kümmern.



  • @zeropage sagte in Was macht man mit solch einer Fehlermeldung?:

    Hm, was wäre denn besser? Ist ja nocht temporär.

    Ein array zum Beispiel. Ich weiß nicht, was das mit "temporär" zu tun hat.

    Wenn ich das Rect tranformieren will, benötige ich doch die Points?

    Ich verstehe nicht, was das mit der Frage, ob ein dynamisches Array (aka vector) das richtige ist, zu tun haben könnte.



  • @titan99_

    width ist gleich size() und das letzte Element ist size() - 1. Deshalb nehme ich nur die Punkte.

    Mein KO-System wird schon bei der Erstellung so gespiegelt, das y0 unten ist.



  • Der Vorteil bei 2 Punkten ist, dass garantiert ein Rechteck beschrieben wird. Wenn du einzelne Punkte bei deinem „Rechteck“ von außen veränderst (was ja bei einem Entwurf möglich war/ist), gilt dies nicht mehr.
    Ich würde, wenn du schon einen vector für die Punkte nimmst, mit einem allgemeinen Polygon anfangen. So kann auch aus einer Gerade ein Dreieck, aus einem Dreieck ein Viereck und daraus ein beliebiges Vieleck werden.
    Rotation, Skalierung, Hittest und Darstellung können für alle gleich behandelt werden und bei „kaputten“ Polygonen muss man ohnehin schauen, was man damit anstellen möchte (crossing/winding number).
    Spezialfälle können dann, falls nötig, später gesondert betrachtet werden.
    Vermutlich wäre es auch besser, die Originalpunkte separat zu speichern und die Darstellung und sonstigen Berechnungen dann mit anderen (eben den transformierten) Daten durchzuführen.



  • @zeropage sagte in Was macht man mit solch einer Fehlermeldung?:

    	val_t right() const { return points_[2].x(); }
    	val_t top() const { return points_[2].y(); }
    

    Sowas ist halt extrem ungut, wenn du nicht sicherstellst, dass "points_" wirklich mindestens 3 Elemente hat. Wie z.B. im Falle deines Default-Konstruktors. In dem Fall wäre dein Vector nämlich leer.
    Deswegen macht man solche "getter" eigentlich nicht.

    Im Fehlerfall bekommst du hier einen Segmentation-Fault und hast keine Ahnung wo dir das Ding gerade um die Ohren geflogen ist. Oder du bekommst irgendwelche Werte zurück, und hast plötzlich ein merkwürdig geformtes Rechteck.

    Ich schließe mich hier den Sportfreunden an, die sagen: Lieber das Rechteck durch zwei Punkte definieren ( oben-links, unten-rechts ). Vectoren oder Arrays sind hier nicht nötig.

    beim Vector bist du zudem noch vom Allokator abhängig, der möglicherweise, jetzt schon oder auch in Zukunft, mehr Speicher anfordert, als für 4 Elemente nötig ist. Garantieren kann dir das niemand.



  • @zeropage sagte in Was macht man mit solch einer Fehlermeldung?:

    @titan99_
    width ist gleich size() und das letzte Element ist size() - 1. Deshalb nehme ich nur die Punkte.

    Das hast du wohl mißverstanden: mit width und height sind die Ausmaße des Rechtecks gemeint, d.h.

    width = abs(right - left) + 1
    height = abs(bottom - top) + 1
    

Anmelden zum Antworten