Value type



  • Hallo an alle,

    ich bekomme von einem Objekt ein Ansammlung von ints und doubles. Die sollen aber in einem andere Objekt gespeichert werden.

    Nun habe ich eine Klasse designt die nur Setter und Getter hat.

    Ungefähr so:
    [cpp]
    class C
    {
    public:
    C(); // Initilisiert Membervaribable.

    void SetD1( double d1 ) { m_d1 = d1 }
    double GetD1() const { return m_d1 }
    // weitere Setter und Getter.

    private:
    double m_d1;
    // etc
    };

    Die Setter Methode wird von Empfänger Objekt benutzt, die Getter von dem Objekt, wo die Daten gespeichert werden. Eigentlich finde ich es soweit ganz ok. Trotzdem beschleicht mich irgendwie das Gefühl, dass die Klasse eine "Empty" Klasse sein könnte, sie tut nichts, ausser die Werte zu kopieren.

    Kennt ihr noch andere Lösungswege?

    Gruß
    Markus



  • Setter, die nur zuweisen, sind imho unnötig. Da kann man auch direkt zuweisen. Du solltest, wenn möglich und sinnvoll, die Werte vor der Zuweisung auf Gültigkeit überprüfen und den Setter vielleicht auch einen Fehlercode zurückgeben lassen.



  • _matze schrieb:

    Setter, die nur zuweisen, sind imho unnötig.

    Nö, sind sie nicht. Felder sollten *immer* privat sein. Weder öffentlich noch protected. Denn dort kann sich durchaus mal was an der Implementierung ändern, und das sollte möglichst vollzogen werden, ohne, dass sich die Schnittstelle der Klasse ändert. Denn wenn diese sich ändert, muss überall dort, wo die Klasse verwendet wird, Code geändert werden.

    Das ist nicht nur ärgerlich, das ist teuer. Aus diesem Grund hält man Änderungen lokal, und das erreicht man, indem man die Schnittstelle von der Implementierung trennt – z.B. durch Setter.



  • Konrad Rudolph schrieb:

    _matze schrieb:

    Setter, die nur zuweisen, sind imho unnötig.

    Nö, sind sie nicht. Felder sollten *immer* privat sein. Weder öffentlich noch protected. Denn dort kann sich durchaus mal was an der Implementierung ändern, und das sollte möglichst vollzogen werden, ohne, dass sich die Schnittstelle der Klasse ändert. Denn wenn diese sich ändert, muss überall dort, wo die Klasse verwendet wird, Code geändert werden.

    Das ist nicht nur ärgerlich, das ist teuer. Aus diesem Grund hält man Änderungen lokal, und das erreicht man, indem man die Schnittstelle von der Implementierung trennt – z.B. durch Setter.

    Hmm, irgendwie wahr... gut, dem habe ich nichts entgegenzusetzen! 😉



  • @Konrad Rudolph:
    Ich finde das kommt darauf an. U.a. darauf wie breit das Interface eingesetzt wird. Wenn die Klasse lokal in einem Modul ist finde ich es nicht weiter tragisch da einfach offene structs zu verwenden.

    Wenn es sich um eine Klasse handelt die an 100 Stellen verwendet wird... hm. Vielleicht. Allerdings wird sowas sowieso so schnell problematisch wenn man da was ändern muss, mit oder ohne Setter/Getter. Von daher.... hm. Hmmmmmmmmmm. 🙂



  • hustbaer schrieb:

    @Konrad Rudolph:
    Ich finde das kommt darauf an. U.a. darauf wie breit das Interface eingesetzt wird. Wenn die Klasse lokal in einem Modul ist finde ich es nicht weiter tragisch da einfach offene structs zu verwenden.

    Ja, das habe ich schon sooo oft gedacht „och, diese Klasse wird ja nur hier lokal eingesetzt“ – und dann habe ich sie doch wieder für andere Dinge verwendet. Ne, Vorsicht ist besser als Nachsicht. Diese kleinen Hilfsklassen haben die doofe Angewohnheit, immer größer und globaler zu werden. Gerade bei „einfachen“ Recordsets passiert mir das immer wieder.

    Ehrlich gesagt gibt es durchaus auch Situationen, in denen ich Datenkapselung für Felder auslasse. Letzendlich ist es immer eine Frage des gesunden Menschenverstands. Aber: ich würde niemandem den Tip geben, das auch so zu tun. Do as I say, not as I do.



  • Meiner erste Idee und auch Umstetzung war ein Interface mit getter zu definieren, von einer Klasse abzuleiten und die setter zu schreiben. Hiermit hätte ein Benutzer niemals die Werte überschreiben könnnen. Dann vergaß ich natürlich das Objekt wieder wegzuräumen und auch die Tatsache, dass es sich um "build-in" Typen handelt etwas übertrieben.

    Da ich zur Zeit nur einmal am Anfang kopiert wird, fand ich die Möglichkeit mit den Gettern und Settern ok. Später kam mir allerdings auch der Gedanke, dass ich es mit struct machen könnte, die einmal initialisiert werden und dann gelesen. So oder so das Objekt hat eine Kopie. Ich denke, dass ich mir vielleicht etwas zu viel Gedanken über die Schreibrechte gemacht habe.

    Allerdings hat es mich mal interessiert ob sich ausßerhalb meiner Firma eine Strategie durchgesetzt hat - was wohl nicht der Fall ist.



  • hustbaer schrieb:

    @Konrad Rudolph:
    Ich finde das kommt darauf an. U.a. darauf wie breit das Interface eingesetzt wird. Wenn die Klasse lokal in einem Modul ist finde ich es nicht weiter tragisch da einfach offene structs zu verwenden.

    Wenn es sich um eine Klasse handelt die an 100 Stellen verwendet wird... hm. Vielleicht. Allerdings wird sowas sowieso so schnell problematisch wenn man da was ändern muss, mit oder ohne Setter/Getter. Von daher.... hm. Hmmmmmmmmmm. 🙂

    Es ist ganz simpel eigentlich:

    Entweder sind alle Daten-Member public oder private. Egal was man macht, alle Daten-Member müssen die selben Access Rechte haben.

    Nun kommt die Entscheidung ob public oder private:
    Wenn Member voneinander abhängen - es also Invarianten gibt die zwischen den einzelnen Membern gibt, dann müssen sie private sein. Gibt es keine Invarianten, dann sind zB die Member einfach nur lose zusammenhängende Daten die der einfachheit halber in eine struct gepackt wurden - dann ist die struct nur eine vereinfachung für x einzelne variablen die man halt nicht händisch überall hin reichen will.

    private und public Member zu mischen ist nie gut. Und protected member sind so gut wie public, da kann man sie gleich public machen...



  • @Shade of Mine: gut zusammengefasst. Full ACK.



  • Ja, klingt vernünftig.
    Ich meinte auch die "keine Invarianten, alles public" Teile.



  • ich stimme dem auch zu, würde sogar noch weiter gehen: soche "bundle-of-data" structs sollten bis auf ganz wenige Ausnahmen keinerlei Methoden haben. (und was sie an Methoden haben sollte auch durchgehend public sein)

    Der Grund: Der Sinn von Methoden ist meist, Funktionalität zu liefern und gleichzeitig bei Zugriffen auf die Daten einer Klasse Invarianten zu gewährleisten. bei public-structs gibts aber keine Invarianten und die Funktionalität besteht daraus, Werte zu halten.

    Die einzigen Methoden die man sich vorstellen kann sind also:
    - Konstruktoren, um konstante Member und Member ohne Default-Ctor angemessen zu initialisieren
    - Methoden, die häufig benutzte "Massenzugriffe" bündeln und keine zusätzliche Funktionalität liefern. Solche Methoden verstoßen allerdings gegen das Prinzip der minimalen Schnittstelle (die public member SIND die Schnittstelle) und sind nicht mehr als ein Kompromiss an die Faulheit des Entwicklers 😉



  • Wie sieht es eigentlich mit solchen POD-Structs aus, wenn man da nur Konstruktoren und Destruktor reinschreibt, hat das irgendwelche Auswirkungen auf das Speichermanagement bzw. memset, memcpy etc.?
    Eigentlich dürfte das doch nicht stören, oder?



  • POD structs sind nicht das Selbe wie structs die nur zur Datenbündelung dienen.
    PODs haben afaik keine eigenen Konstruktoren außer den standardmäßig gelieferten, und sie bestehen nur aus nativen datentypen.
    public Datenbündel dagegen können sehr wohl auch andere Typen beinhalten, Bsp:

    struct FunnyBundle
    {
      const std::string name;
      int someInt;
      Foo someFoo;
    
      FunnyBundle(std::string s) : name(s) {}
    };
    

    Das FunnyBundle hat keinen Default-Ctor, ist nicht mit C-memXXX-Funktione zu behandeln usw.
    Destruktoren sollten Bündel-structs übriges auch nicht haben, da müssen die member defualt-zerstörbar sein. Alles was ein Destruktor kann (z.B. irgendwelche Ressourcen freigeben), kann auch vor dem Dtor-Aufruf schon passiert sein. Wenn man sich andererseits darauf verlassen muss, dass ein Dtor irgendeine Arbeit macht, ist das eine Invariante die zur Folge hat dass man alles in eine private klasse kapseln sollte. Die einzige Invariante die man tolerieren kann ist constness von einzelnen membern.

    auch bei POD-structs würde ich keine memXXX-Funktionen verwenden sondern die normalen Zugriffe bzw. compilergenerierten Copy-Ctoren und andere C++-Features nutzen. Wenn ich z.B. bei ner memXXX-Funtkion irgendwo mitten im POD (z.b. nach dem 7ten byte eines double) schluss mache hab ich zwar keine Specherlecks, Objektleichen etc. (die nativen Datentypen haben keine ungültigen zustände und PODs keine invarianten), dafür aber ne Menge Datenmüll, z.B. sinnfreie Zahlenwerte und Pointer ins Nirvana. Man darf ein POD zwar mutwillig verhunzen (dazu sind die Member ja public), aber wenn ich das wirklich will mache ich das deutlich und nicht hintenrum mit irgendwelchen C-Funktionen 😉



  • Hab bisher auch noch keine De- oder Konstruktoren dort eingesetzt. Nur kämpfe ich mich meist durch Structs die nur andere Structs und primitive Datentypen beinhalten usw. Dies bis zu einer Tiefe von ca. 20 Schritten oder mehr inklusive Vererbung und dort geht es dann ebenso weiter. Natürlich sind auch Pointer dabei auf eben solche weiteren Strukturen.

    Da dann ohne Destruktor zu arbeiten ist ziemlich deprimierend, da an denen Stellen wo gelöscht wird, auch immer die ganzen Member manuell durchgegangen werden müssen. Die Verwendung eines Destruktors (sei es auch nur eine stumpfe Methode die das übernimmt) liegt da natürlich nahe.



  • Fellhuhn schrieb:

    mehr inklusive Vererbung und dort geht es dann ebenso weiter.

    Structs mit vererbung sind keine PODs mehr.
    Wenn du Member explizit zerstören musst mittels Destruktor dann sind diese Member keine PODs. structs sind keine PODs mehr, wenn sie Pointer beinhalten die Besitzrechte an den referenzierten Objeten haben (sprich wenn das struct für die Zerstörung der referenzierten Objkete zuständig ist).

    Wenn ein Objekt nicht ohne weiteres leise wegsterben kann ist das ein Hinweis darauf dass die beinhalteten Daten gekapselt werden müssen. Den Pointer-Fall kann man noch flicken indem man ein Datenbündel mit Smart-poitnern draus macht.



  • Pumuckl: „Keine Methoden“ halte ich grundsätzlich für eine schlechte Idee. Zu einer Struktur gehörige Methoden gehören nunmal logisch zusammengruppiert. In C++ würde man das anders machen, aber in C# gehören diese Methoden dann eigentlich *in* die Struktur.

    Die Invarianten lassen sich leicht gewährleisten, wenn man die Strukturen immutable macht. Das ist sowieso meist eine gute Idee.



  • Konrad Rudolph schrieb:

    Pumuckl: „Keine Methoden“ halte ich grundsätzlich für eine schlechte Idee. Zu einer Struktur gehörige Methoden gehören nunmal logisch zusammengruppiert. In C++ würde man das anders machen, aber in C# gehören diese Methoden dann eigentlich *in* die Struktur.

    Die Invarianten lassen sich leicht gewährleisten, wenn man die Strukturen immutable macht. Das ist sowieso meist eine gute Idee.

    Mit Methoden meinte ich Methoden im erweiterten Sinn, also grundsätzlich jede Funktion, die logisch zu einer Klasse gehört (z.B. op<< und andere Knadidaten, die standardmäßig außerhalb der Klasse deklariert, aber mit ihr ausgeliefert werden).
    Methoden bearbeiten die Member des Structs. Da aber eh alle Member öffentlich zugänglich sind, kann diese Bearbeitung auch ohne die Methoden geschehen, somit sid die Methoden wie oben gesagt ein Erweiterung der Schnittstelle.



  • Shade Of Mine schrieb:

    Entweder sind alle Daten-Member public oder private. Egal was man macht, alle Daten-Member müssen die selben Access Rechte haben.

    Wie siehts mit statischen Membern aus? Vor allem statische Konstanten und Enums sollten doch sicher public sein. Bei statischen, nicht-konstanten Klassenvariablen kann man sich drüber streiten. Allerdings würde ich das so oder so unabhängig von den Instanzvariablen sehen.



  • Nexus schrieb:

    Wie siehts mit statischen Membern aus? Vor allem statische Konstanten und Enums sollten doch sicher public sein. Bei statischen, nicht-konstanten Klassenvariablen kann man sich drüber streiten. Allerdings würde ich das so oder so unabhängig von den Instanzvariablen sehen.

    konstanten sind keine variablen. Alles was konstant ist, kann ruhig public sein. sofern sich der wert nie ändert (zB wird erst später initialisiert oder so).

    statische variablen sollten aber wie gehabt zu den anderen access rechten passen.

    wobei man hier uU einen schritt weiter gehen will und alle statischen variablen private deklarieren will. wenn man genauer bedenkt, dann kenne ich aber keinen fall indem eine struct mit lauter public variablen eine statische member variable hatte... insofern ist statische variablen immer private machen sicher nicht verkehrt.



  • Shade Of Mine schrieb:

    insofern ist statische variablen immer private machen sicher nicht verkehrt.

    Ich hatte da einen Fall, wo ich kein Singleton verwenden wollte, und deshalb bei einer Klasse alles statisch gemacht habe - nicht gerade das ideale Design, ich weiss (aber mir gefällt der Operator :: eben so, und vereinheitlicht auch alles, wenn ich noch Enums habe). 😉

    Nein, das mit den einheitlichen Zugriffsrechten klingt für mich eigentlich logisch (ich wüsste auch keinen Fall, wo man statische Variablen öffentlich bräuchte).

    Noch was zu Strukturen: Das ist vielleicht Ansichtssache, aber für mich ist eine struct , die mehr als nicht-statische Membervariablen und einen Konstruktor anbietet, schon eher eine Klasse ( class ).

    Shade Of Mine schrieb:

    Alles was konstant ist, kann ruhig public sein. sofern sich der wert nie ändert (zB wird erst später initialisiert oder so).

    Sorry, ich kann mir jetzt nicht gerade vorstellen, wie man auf normalem Wege eine konstante, nicht-statische Variable nach dem Konstruktor initialisieren kann.

    Btw:

    Shade Of Mine schrieb:

    konstanten sind keine variablen.

    Begrifflich gesehen stimmt das zwar, aber ich bezeichne auch Konstanten per const als Variable, weil sie Werte aufnehmen können (das ist zwar eine ungenaue Definition, aber ihr wisst, was ich meine). Aber das ist meine Sichtweise, und in C++ sind sowieso einige Begriffe nicht ganz eindeutig...


Anmelden zum Antworten