Guter Stil oder Overhead?



  • Bisher sieht es so aus:

    #pragma once
    
    enum ECardSymbols {
    	eDiamond=0,
    	eHeart=1,
    	eClub=2,
    	eSpade=0
    };
    
    enum ECardValues {
    	eAce=1,
    	eTwo=2,
    	eThree=3,
    	eFour=4,
    	eFive=5,
    	eSix=6,
    	eSeven=7,
    	eEight=8,
    	eNine=9,
    	eTen=10,
    	eJack=11,
    	eQueen=12,
    	eKing=13
    };
    
    class CCard {
    private:
    	ECardSymbols m_Symbol;
    	ECardValues m_Value;
    	CCard();
    public:
    	bool operator==(const CCard& other) const;
    	CCard(ECardSymbols Symbol, ECardValues Value);
    	~CCard();
    };
    


  • Ach, das ist kein Problem. Ich hätte move vorgeschlagen, aber bei dem Quatsch ist das auch nicht schneller. Mach es so wie bisher. 🙂

    PS:
    #pragma once ist nicht standard.
    C vor Klassennamen zu schreiben ist sowas von out. (Genau wie E vor enums.)



  • Dudeldu schrieb:

    Bisher sieht es so aus:

    enum ECardSymbols {
    	eDiamond=0,
    	eHeart=1,
    	eClub=2,
    	eSpade=0
    };
    

    Zwei Mal 0?

    Wieso überhaupt Nummern? Enumeratoren werden implizit aufsteigend und von Null beginnend nummeriert, explizit hinzuschreiben brauchst du die Nummern nur in Ausnahmefällen.

    Dudeldu schrieb:

    class CCard {
    private:
    	ECardSymbols m_Symbol;
    	ECardValues m_Value;
    	CCard();
    public:
    	bool operator==(const CCard& other) const;
    	CCard(ECardSymbols Symbol, ECardValues Value);
    	~CCard();
    };
    

    Warum Default-Konstruktor und Destruktor? Beide sind unnötig.

    operator== würde ich als globale Funktion implementieren (wie folgt), ausserdem wäre ein operator!= als Gegenstück vielleicht sinnvoll.

    bool operator==(const Card& lhs, const Card& rhs);
    

    Und wie schon angetönt sind C- und E-Präfixe antiquiert und bringen längerfristig mehr Nachteile als Vorteile, da du sie eh nicht konsequent umsetzen kannst. Ich habe das vor einiger Zeit ausführlich begründet.



  • Beim ersten enum hab ich nun die zahlen entfernt.
    Beim zweiten enum möchte ich nicht bei 0 Anfangen, weil die Wertigkeit auch zum Wert der Karte passt.

    Ich habe die Klasse mit Visual Studio erstellt, dieses hat mir automatisch den Constructor und Destructor erstellt.
    != hab ich nun auch implementiert.

    Warum würdest du '==' global definieren?
    Ich sehe da gerade keinen Vorteil bzw. unterschied...

    Die Prefixe hab ich rausgenommen, die hatte ich auch nur bei Klassen und nicht bei anderen Typen.



  • Meines Wissens kannst du auch dann einfach schreiben:

    enum CardValues {
        Ace = 1,
        Two, // zählt automatisch weiter
        // usw
    }
    

    EDIT:

    Warum würdest du '==' global definieren?

    meinst du public / öffentlich? Du kannst ihn (den Operator) sonst doch gar nicht außerhalb verwenden (?)



  • Nexus schrieb:

    operator== würde ich als globale Funktion implementieren (wie folgt), ausserdem wäre ein operator!= als Gegenstück vielleicht sinnvoll.

    bool operator==(const Card& lhs, const Card& rhs);
    

    ich bezog mich auf diese Aussage mit "Global".



  • Weil man Operationen auf Objekte nur dann als Memberfunktion definieren sollte, wenn sie etwas mit der Kapselung der Klasse zu tun haben. Wenn m_Symbol und m_Value also von außen zugänglich sind (z.B. durch getter Funktionen), dann mach den Vergleich zu einer freien Funktion. Sonst lass es so.



  • cooky451 schrieb:

    Weil man Operationen auf Objekte nur dann als Memberfunktion definieren sollte, wenn sie etwas mit der Kapselung der Klasse zu tun haben. Wenn m_Symbol und m_Value also von außen zugänglich sind (z.B. durch getter Funktionen), dann mach den Vergleich zu einer freien Funktion. Sonst lass es so.

    Das ist immer noch keine Begründung, sondern nur die Aussage, dass man es so machen soll.



  • hmmmm schrieb:

    Das ist immer noch keine Begründung, sondern nur die Aussage, dass man es so machen soll.

    Das stimmt. Und die Begründung ist auch nicht völlig unkontrovers, aber es ist recht akzeptierter C++ Stil. Die Kapselung der Klasse wird meiner Meinung verbessert, je sauberer (und kleiner) das Interface ist, und desdo weniger Schnittstellen es somit nach außen gibt, auf die man achten muss. Ist hier natürlich trivial, aber ich ziehe es generell vor.
    Falls dich das nicht überzeugt, google mal nach "prefer non-member non-friend functions". Wenn dich das immer noch nicht überzeugt, mach's halt anders, aber behalte im Hinterkopf, dass es recht viele Leute überzeugt hat.



  • cooky451 schrieb:

    hmmmm schrieb:

    Das ist immer noch keine Begründung, sondern nur die Aussage, dass man es so machen soll.

    Das stimmt. Und die Begründung ist auch nicht völlig unkontrovers, aber es ist recht akzeptierter C++ Stil. Die Kapselung der Klasse wird meiner Meinung verbessert, je sauberer (und kleiner) das Interface ist, und desdo weniger Schnittstellen es somit nach außen gibt, auf die man achten muss. Ist hier natürlich trivial, aber ich ziehe es generell vor.
    Falls dich das nicht überzeugt, google mal nach "prefer non-member non-friend functions". Wenn dich das immer noch nicht überzeugt, mach's halt anders, aber behalte im Hinterkopf, dass es recht viele Leute überzeugt hat.

    Kenne ich und hat mich noch nie überzeugt. Interessant ist auch, dass es nie einer wirklich gut begründen kann. Es kommen immer nur so Aussagen, wie google mal ... lies mal den Blog...



  • Eigentlich ist die Sache klar. Es ist eine der wichtigsten Erkenntnisse der Softwaretechnik, dass man die Abhängigkeiten zwischen Komponenten so weit wie möglich verringern sollte (lose Kopplung) und die Komponenten in sich kompakt gehalten werden sollen (hohe Kohäsion).
    Und genau das wird u.A. durch ein schlankes Interface erreicht.



  • JFB schrieb:

    Eigentlich ist die Sache klar. Es ist eine der wichtigsten Erkenntnisse der Softwaretechnik, dass man die Abhängigkeiten zwischen Komponenten so weit wie möglich verringern sollte (lose Kopplung) und die Komponenten in sich kompakt gehalten werden sollen (hohe Kohäsion).

    Ja.

    Und genau das wird u.A. durch ein schlankes Interface erreicht.

    Und das ist wieder keine Begründung, warum alles mögliche in freie Funktionen stecken dazu führen sollte, sondern nur die Behauptung, dass es so wäre.
    Warum erhöht es die Abhängigkeit zwischen Komponenten, wenn ich eine toUpper-Methode in eine String-Klasse stecke? Warum sollte ich weniger Abhängigkeiten haben, wenn ich eine freie Funktion toUpper(string) habe?



  • Angenommen du nutzt eine Klasse die nicht von dir geschrieben ist, oder eine Klasse die anderweitig ein festes Interface hat und nicht verändert werden sollte. (z.B. weil sie bereits getestet ist etc.)
    Wenn du sie jetzt erweiterst, änderst du ihr Interface, musst sie neu testen etc. Mit einer freien Funktion musst du das nicht. Insbesondere wenn es eh nicht dein Code ist, wirst du eine freie Funktion vorziehen. Wenn du das vorher aber nicht gemacht hast, wird es nicht mehr einheitlich.



  • Warum erhöht es die Abhängigkeit zwischen Komponenten, wenn ich eine toUpper-Methode in eine String-Klasse stecke? Warum sollte ich weniger Abhängigkeiten haben, wenn ich eine freie Funktion toUpper(string) habe?

    Das geht am Punkt vorbei, toUpper ist nichts, was ich persönlich aus den von mir genannten Erwägungen in eine eigene Komponente verladen würde.
    Abgesehen davon sind freie Funktionen im OOA eh nicht wirklich präsent.

    Aber unabhängig von der Operatorüberladung, im Allgemeinen ist das eine Gratwanderung:
    Auf der einen Seite ist das "Minimalinterface", eine ausgesprochen hässliche Lösung: Getter und Setter für alle Interna, der Rest wird über "Manipulator"-Komponenten gelöst. Das ist Quatsch und trägt nichts zur Auflösung von Abhängigkeiten bei, sondern stellt eben gerade neue (zwischen Komponente und Manipulator) her (hohe Kohäsion innerhalb der Komponenten, aber leider auch starke Kopplung).
    Auf der anderen dagegen das Monolith-Antipattern: Eine Komponente ist für alle Operationen verantwortlich, die mit ihr zu tun haben (sehr niedrige Kohäsion, dafür kaum Kopplung). Man muss nur in den richtigen Momenten den richtigen Weg wählen, um beide Maße an ihr Optimum anzunähern.

    Ein Beispiel: Das Visitor-pattern. Im simplen Fall von Binärbaumtraversierungen wird die Motivation schon sichtbar: Es ist nicht Aufgabe eines Binärbaums, sich selbst Pre-, Post- oder Inorder zu durchlaufen. Das ist Logik, die man außerhalb der Datenverwaltung (dem Baum) unterbringen möchte. Das erhöht die Kopplung zwar, verringert aber auch die Kohäsion. Ob und wie sehr sich das lohnt muss man im Einzellfall entscheiden.



  • JFB schrieb:

    Warum erhöht es die Abhängigkeit zwischen Komponenten, wenn ich eine toUpper-Methode in eine String-Klasse stecke? Warum sollte ich weniger Abhängigkeiten haben, wenn ich eine freie Funktion toUpper(string) habe?

    Das geht am Punkt vorbei, toUpper ist nichts, was ich persönlich aus den von mir genannten Erwägungen in eine eigene Komponente verladen würde.
    Abgesehen davon sind freie Funktionen im OOA eh nicht wirklich präsent.

    Naja, das war der Punkt, aber sonst kann ich dir zustimmen.



  • Im Falle der operatoren ist nicht nur Kapselung sondern auch Symmetrie ein Thema.
    Ist a == b etwas komplett anderes als b == a ? Nicht, oder? Beide sind gleichwertig, deshalb sollte der operator== dies auch ausdrücken, und nicht an irgend ein einzelanes Objekt gebunden sein.



  • hmmmm schrieb:

    Warum erhöht es die Abhängigkeit zwischen Komponenten, wenn ich eine toUpper-Methode in eine String-Klasse stecke? Warum sollte ich weniger Abhängigkeiten haben, wenn ich eine freie Funktion toUpper(string) habe?

    Du hast toUpper von der Stringklasse entkoppelt und kannst Spezialisierungen fuer andere Stringklassen anbieten, ja selbst fuer std::vector<char> std::array<char, 15> oder char -Arrays, die selbst keine Klassen sind. Und mit Templates musst du toUpper wahrscheinlich nur einmal implementieren.





  • knivil schrieb:

    hmmmm schrieb:

    Warum erhöht es die Abhängigkeit zwischen Komponenten, wenn ich eine toUpper-Methode in eine String-Klasse stecke? Warum sollte ich weniger Abhängigkeiten haben, wenn ich eine freie Funktion toUpper(string) habe?

    Du hast toUpper von der Stringklasse entkoppelt und kannst Spezialisierungen fuer andere Stringklassen anbieten, ja selbst fuer std::vector<char> std::array<char, 15> oder char -Arrays, die selbst keine Klassen sind. Und mit Templates musst du toUpper wahrscheinlich nur einmal implementieren.

    Wie frage ich std::vector<char> nochmal nach seinem Encoding?



  • Wie fragst du denn std::string?


Anmelden zum Antworten