Set- und Getterfunktionen verwenden oder nicht?



  • NewSoftzzz schrieb:

    Wer jetzt noch widerspricht, bitte Nachteile nennen oder Gegenteil beweisen.

    Ich empfinde es beispielsweise als Nachteil, wenn ich eine Funktion nicht sehr oft aufrufe, dass da immer ein statischer std::string im RAM rumgeistert, der nur äußerst selten benötigt wird, nur um genau dann etwas minimal schneller zu sein.
    Was sicherlich nett sein kann, ist dass der std::string auch als const deklariert ist und somit in einem Read-Only-Bereich im Speicher liegt, das kann schon nochmal schneller werden.

    Ich muss aber auch sagen, dass ich selber noch keine wirklich zeitkritischen Programme entwerfen musste. Ich freu mich, wenn das Programm-Design mir (und anderen) gefällt, wenn ich neue Sachen lerne und umsetzen darf und dabei ein schönes Programm noch schneller wird.

    Aber aus dieser möglichen minimalen Verbesserung der Laufzeit in einem möglicherweise vernachlässigbaren Teil der Gesamtlaufzeit eine Fixe Regel zu machen finde ich übertrieben. Deine Angabe von 1600% mehr Speed greift nur, wenn ich tatsächlich im Programm nichts anderes mache als jeden mir vom OS zur Verfügung gestellten Prozessortakt mit std::strings an Funktionen übergeben verbringe. Und das ist eine Annahme, die ich mich ehrlich gesagt nie trauen würde auszusprechen.

    Im übrigen hätte ich dein Problem nicht durch lauter im Programm verteilte static-strings gelöst. Wenn du das in ner Funktion machst, und den selben String in einem anderen Teil - sprich Scope - auch ausgeben willst, musst du den nochmal als static ablegen.
    Ein Singleton, der alle Strings fasst und dem du mit nem Enum oder direkten Funktionsaufrufen die Strings entlocken kannst, fände ich angenehmer zu bedienen. Bei Rechtschreibkorrekturen oder Übersetzungen hast du auch eine zentrale Anlaufstelle, was die Suchzeit verkürzt.
    Weiterer Vorteil: Ich kann bei Programmstart den Singleton (und damit alle Strings) initialisieren. Bei der Version mit in Funktionen verteilten static strings werden diese erst beim ersten Aufruf initialisiert, was genau da wieder etwas länger dauert. Unter der Annahme, dass dein Server eh durchgehend läuft, wird auch der anfangs höhere Speicherverbauch durch noch nicht genutzte Strings wieder eliminiert.
    Dieses Konzept weitergedacht, führt dazu, dass jedes Level sicher eigene Messages an den Player schicken wird, manche Sachen (wie Warnungen, allgemeine Anreden, usw.) ändern sich nicht. Also geben wir dem "MessageProvider" verschiedene "MessageBackends" für verschiedene Aufgaben. Manche statisch, manche ändern sich je nach geladenem Level/teilnehmenden Charakteren/....

    So haben wir die static const std::string-Deklarationen aus dem Code verbannt und noch zusätzliche Flexibilität gewonnen!



  • Nexus schrieb:

    Wo genau in dem Zitat hast du etwas von Compilezeit erwähnt?

    4 mal wörtlich und dann noch mehrmals sinngemäß (ca. in 10 VERSCHIEDENEN Posts hab ich das erwähnt)..
    Soll ich Zitate raussuchen, um dich noch weiter zu blamieren?

    l'abra d'or schrieb:

    Ich empfinde es beispielsweise als Nachteil, wenn ich eine Funktion nicht sehr oft aufrufe, dass da immer ein statischer std::string im RAM rumgeistert, der nur äußerst selten benötigt wird, nur um genau dann etwas minimal schneller zu sein.

    Wenn ich ein Programm ausführe, wird die Executable in den RAM geladen. Somit ist auch ein c-string-literal ständig im RAM... Es gibt also KEINEN Speichernachteil (wenn es den geben würde, hätte ich das Dogma niemals aufgestellt).

    l'abra d'or schrieb:

    Ein Singleton, der alle Strings fasst

    Das ist auch ein static const string, nur anders verpackt. An sowas hab ich natürlich auch gedacht und passt hervorragend in das Dogma rein.

    Athar schrieb:

    Eine Faustregel, die man immer anwenden kann/sollte und die ich auch schon erwähnt hatte, ist: wenn der Zeitaufwand für die Optimierung die Summe der Laufzeiteinsparungen aller jemals erwarteten Programmdurchläufe übersteigt, lohnt sich die Optimierung mit großer Sicherheit nicht.

    Der Regel stimme ich zu 100% zu. Ich brauche für die zusätzliche Zeile ca. 10 Sekunden, die ich nur in dem Benchmark schon wieder rausgeholt habe. Widerspricht dem Dogma also auch nicht 😉

    [Ghost] schrieb:

    Du willst mir sagen, dass du beides gleich leserlich findest? Ehm, gut ... wenn du meinst ... ich bin da anderer Meinung ... 😮

    Wenn du die Text1-4 nennst, dann garantiert nicht. Ich hab aber auch schon 2 mal geposted, dass der Variablenname am besten eine kurze Zusammenfassung des Textes sein soll.

    Nexus schrieb:

    Die Tatsache, dass du den Begriff "Dogma" derart verharmlost, ist bedenklich.

    Das ist Selbstironie. Natürlich sollte niemand sein Gehirn ausschalten und sobald jemand eine Ausnahme der Regel findet (eine Ausnahme wären Codestellen, die nur 1 mal während der kompletten Runtime ausgeführt werden), darf er die auch gerne posten...

    mfg, René~



  • theliquidwave schrieb:

    Hui.
    Erstmal alles verstehen 😃
    Ich muss mich morgen dann mal genauer mit der std::string-Klasse befassen. Ich dachte da wohl immer komplett in die falsche Richtung 😶

    @ tntnet: Also sollte der Prototyp so:

    inline const CClass *CClassManager::getClass(const char *pszName)
    

    oder so:

    inline CClass *CClassManager::getClass(const char *pszName) const
    

    aussehen? Ich habe beides schon des öfteren gesehen und beides funktioniert.
    Wo ist der Unterschied und was ist in diesem Fall richtig?

    Gruß

    Ich würde es am ehesten so schreiben:

    inline const CClass &CClassManager::getClass(const char *pszName) const
    

    . Zur Not auch so:

    inline const CClass *CClassManager::getClass(const char *pszName) const
    

    . Ich vermeide halt Zeiger. Gerade wenn ich garantiere, dass ich wirklich eine Klasse zurück gebe und keinen Null-Zeiger, dann lieber als Referenz.

    Das const hinten sagt, dass die Methode den Zustand der Klasse nicht verändert. Das const vorne sagt, dass Du das, was du zurück bekommst, nicht ändern darfst. Es kommt natürlich auf den Anwendungsfall an. Ich weiß nicht so recht, was Du erreichen willst. Wenn es eine Factory-Methode oder ein Sigleton sein soll, dann eventuell auch ohne const am Rückgabewert.

    Dass beides funktioniert bedeutet nicht gleich, dass es elegant ist. Ich kann mit syntaktisch korrekten Programmen sehr viel Unsinn verzapfen. Im Prinzip ist das const nie wirklich notwendig. Es hilft nur, saubere Schnittstellen zu definieren.



  • NewSoftzzz schrieb:

    Wenn ich ein Programm ausführe, wird die Executable in den RAM geladen. Somit ist auch ein c-string-literal ständig im RAM... Es gibt also KEINEN Speichernachteil (wenn es den geben würde, hätte ich das Dogma niemals aufgestellt).

    Dir ist aber schon klar, dass:
    a) Ein std::string Objekt mehr Speicher verbrauchen wird, als ein String-Literal?
    b) Das String-Literal auch im static Bereich liegen wird, zusätzlich zum std::string const Objekt?

    Somit hast du definitiv einen Speichernachteil, welcher ständig vorhanden ist. Auch wenn ich diesen nicht wirklich als Argument aufführen würde, genauso wie ich den zusätzlichen Aufwand zur Erstellung eines temporären std::string Objekt ignoriere, weil dieser Aufwand in den meisten Fällen völlig unwichtig ist.

    NewSoftzzz schrieb:

    Wenn du die Text1-4 nennst, dann garantiert nicht. Ich hab aber auch schon 2 mal geposted, dass der Variablenname am besten eine kurze Zusammenfassung des Textes sein soll.

    Gut, kann sein, dass du dies gesagt hast, ich finde es trotzdem noch unlesbar. Und zwar in den beiden folgenden Formen:

    void foo()
    {
      static std::string const hello = "hello";
      bar(hello);
    
      static std::string const world = "world";
      bar(world);
    
      static std::string const my = "my";
      bar(my);
    
      static std::string const what = "what";
      bar(what);
    }
    
    // oder
    
    void foo()
    {
      static std::string const hello = "hello";
      static std::string const world = "world";
      static std::string const my = "my";
      static std::string const what = "what";
    
      bar(hello);
      bar(world);
      bar(my);
      bar(what);
    }
    
    // gegenüber
    
    void foo()
    {
      bar("hello");
      bar("world");
      bar("my");
      bar("what");
    }
    

    Es ist schlicht und einfach nicht intuitiv und führt zu einer Indirektion beim Lesen. Man fragt sich regelrecht: Was zum Geier soll das?

    Aber wie gesagt, ich kann nicht viel dagegen unternehmen, wenn du das lesbar findest und du kannst nicht viel dagegen unternehmen, dass ich dies unlesbar empfinde.
    Du wirst aber wohl zugeben müssen, dass die Version ohne static const Objekte auch lesbar ist? Und wie man hier sieht, ist es für viele lesbar. Man sollte schliesslich Code nicht so schreiben, dass man nur selbst den Code lesen kann, sondern auch andere.

    Da kommt halt schon die Frage auf:
    Wieso willst du diesen unnötigen Performance Vorteil unbedingt nutzen? Ich meine, wenn du wirklich so auf Performance bedacht bist und das an jedem Ort, egal ob es ins Gewicht fällt oder nicht, dann kannst du gleich C++ in die Tonne schmeissen und wieder mit C arbeiten oder am besten gleich mit Assembler 😉

    Dieser Performance "Verlust" ist in 99% der Fälle locker zu verkraften, also wieso vermeiden?

    Grüssli



  • Off-Topic:
    ... da hat mich doch tatsächlich die Macht der Gewohnheit überlistet, verdammtes 'Grüssli' 😃 :p



  • NewSoftzzz schrieb:

    4 mal wörtlich und dann noch mehrmals sinngemäß (ca. in 10 VERSCHIEDENEN Posts hab ich das erwähnt)..
    Soll ich Zitate raussuchen, um dich noch weiter zu blamieren?

    Ich habe mich extra auf die eine Aussage beschränkt, weil ich zuerst dachte, dass es sich um einen unüberlegten, schnell hingeschriebenen Satz handelte. Wäre ja auch kein Problem gewesen. Aber statt das Ganze zu relativieren und einen offensichtlichen Fehler einzugestehen beharrst du weiterhin darauf, dass sowas wörtlich und immer gilt.

    NewSoftzzz schrieb:

    Natürlich sollte niemand sein Gehirn ausschalten

    Genau das propagieren Dogmen aber, sonst wären sie Richtlinien oder Faustregeln.

    Wie dem auch sei – ich denke, dir ist klar, worauf ich hinauswollte. Wenn du meine Argumente (mit welchen ich ja im Gegensatz zu dir nicht allein stehe) nicht nachvollziehen kannst oder willst, okay. Du könntest in diesem Thread jedoch einiges lernen, wenn du nicht derart in deinen Prinzipien versessen wärst. Merke dir einfach für zukünftige Diskussionen, dass man wenigstens versuchen sollte, auf die Argumente anderer einzugehen. Ich persönlich versuche das auch, und wenn mir das nicht gelingt, bin ich froh um freundliche Hinweise.



  • NewSoftzzz schrieb:

    loks schrieb:

    "Premature optimizing is the root of all evil"

    Und ja, das was Du argumentierst fällt unter diese Regel.

    Nope, bei dem Satz geht es normalerweise um 5% Leistungsverbesserung bei Verschlechterung der Wartbarkeit.

    Bei meinem Dogma geht es um 1640% Leistungsverbesserung ohne Verschlechterung der Wartbarkeit.

    mfg, René~

    Du hast Deine Hasuaufgaben nicht gemacht, denn Du hast offensichtlich _nicht_ nachgelesen was premature optimizing ist, denn diese hat absolut nix mit solchen Zahlen zu tuen.

    Richtiges Optimieren geht so:

    1. Schreib das Programm ohne solche "superoptimierungen" per Dogma.
    2. Benutz einen Profiler um das Laufzeitverhalten zu messen
    3. Analysier die Funktionen die die meiste Rechenzeit im Profiler belegt haben und optimiert diese und nur diese gezielt.
    4. Benutz den Profiler erneut und vergleiche ob die Optimierungen tatsächlich etwas gebracht haben.

    Dann, und NUR dann hast Du sinnvoll optimiert.

    Auch dazu gibt es eine Merkregel: First make it run, than make it fast.

    PUnktuelle Microoptimierungen neigen dazu das sie auf das Gesamtprojekt keine relevante Performance-Auswirkung haben.



  • Nexus schrieb:

    Ich habe mich extra auf die eine Aussage beschränkt, weil ich zuerst dachte, dass es sich um einen unüberlegten, schnell hingeschriebenen Satz handelte.

    Entweder das is jetzt ne ganz billige Ausrede oder du hast das Prinzip einer Internetdiskussion nicht verstanden. (Selbst "die eine Aussage" hatte schon einen Kontext, in dem ich das mit der Compilezeit schon erklärt habe.)

    loks schrieb:

    Dann, und NUR dann hast Du sinnvoll optimiert.

    Danke, ich wusste auch vorher, was premature optimizing ist. Ändert aber nix am Dogma!

    Das Problem der String Aufrufe habe ich damals via Profiler rausgefunden und ja, es ist eine Microoptimierung, aber eine, über die man nicht nachdenken muss. Wenn man das Dogma einfach als durchgängigen Codestil bei jeder literal Stringübergabe benutzt, dann hat man ohne Nachzudenken einen Performancevorteil gewonnen.

    Das fällt eben nicht unter premature optimizing, weil premature optimizing sowas wie Arbeit beinhaltet. Eine andere Schreibweise eines literals (die man sich dann einfach angewöhnen kann) fällt da garantiert nicht drunter.

    mfg~



  • NewSoftzzz schrieb:

    Das fällt eben nicht unter premature optimizing, weil premature optimizing sowas wie Arbeit beinhaltet.

    Das fällt eigentlich nur unter "Entscheide dich zwischen Performance und Speicherverbrauch". Mehr nicht.
    Da permanent durch dich belegter Speicher für konkurrierende Programme wegfällt, und in den meisten (lies: meisten!) Fällen der Performancegewinn minimal ist (=nicht messbar, und bitte nicht wieder dein Benchmark... Mach doch mal was mit dem String und nicht nur mit length...) kannst du dein "Dogma" vllt. so abändern:
    "Wenn das Erstellen temporärer strings ein nicht ertragbarer Performancenachteil ist, halte diese strings permanent im Speicher."
    Da denke ich würde dir jeder zustimmen. Und nicht nur für strings, sondern allgemein für jede Art von Objekt.
    Dass man aber schon vorher weiß, dass diese strings ein Performancenachteil sind, bezweifle ich...



  • l'abra d'or schrieb:

    Das fällt eigentlich nur unter "Entscheide dich zwischen Performance und Speicherverbrauch". Mehr nicht.

    Wenn der static std::string tatsächlich das doppelte an Speicher verbraucht, dann ja. Aber eine compile-time constant sollte der Compiler doch direkt erzeugen (eine const int wird ja auch nicht als variable benutzt sondern überall mit dem literal ersetzt).

    Wenn der static std::string das doppelte an Speicher belegt, revidiere ich meine Aussage.

    Wie können wir das rausfinden?

    mfg, René~



  • NewSoftzzz schrieb:

    Wenn der static std::string das doppelte an Speicher belegt, revidiere ich meine Aussage.

    Wie können wir das rausfinden?

    std::string ist ein Klasse, die zur Laufzeit durch einen Konstruktor erstellt wird. Demnach ist das keine Compilezeit-Konstante.
    Im Binary wird das String-Literal mit in den Speicher geladen, und sobald der static const std::string erstellt wird wird dieses Literal dem Konstruktor übergeben, welcher das Chararray als Kopie zu den Membervariablen legt.
    Ich denke nicht, dass der Compiler da irgend etwas optimieren kann (also das String-Literal direkt als Member anlegen), da der Konstruktor einfach bestimmte Operationen durchführt.

    Da ich aber nicht wirklich Ahnung von Compilern und deren Optimierungsmöglichkeiten habe, kann es sein dass es da doch einen optimierten Ausweg gibt.



  • l'abra d'or schrieb:

    Im Binary wird das String-Literal mit in den Speicher geladen, und sobald der static const std::string erstellt wird wird dieses Literal dem Konstruktor übergeben, welcher das Chararray als Kopie zu den Membervariablen legt.

    Es gibt bestimmt einen const std::string Constructor, der einen Übergebenes c-string-literal als Buffer benutzt, und eben keinen eigenen anlegt? Wenn es den nicht geben würde, wäre das ziemliche Ressourcenverschwendung 😃

    mfg, René~



  • NewSoftzzz schrieb:

    Es gibt bestimmt einen const std::string Constructor, der einen Übergebenes c-string-literal als Buffer benutzt, und eben keinen eigenen anlegt? Wenn es den nicht geben würde, wäre das ziemliche Ressourcenverschwendung

    Und woher sollte dieser Konstruktor wissen, dass die Lebensdauer des übergebenen pointer-auf-const-char "literal" (sprich dauerhaft) ist?



  • NewSoftzzz schrieb:

    l'abra d'or schrieb:

    Im Binary wird das String-Literal mit in den Speicher geladen, und sobald der static const std::string erstellt wird wird dieses Literal dem Konstruktor übergeben, welcher das Chararray als Kopie zu den Membervariablen legt.

    Es gibt bestimmt einen const std::string Constructor, der einen Übergebenes c-string-literal als Buffer benutzt, und eben keinen eigenen anlegt?

    Nein, gibt es aus guten Gründen nicht. Das würde den Zugriff über [] verlangsamen, da jeweils geprüft werden muss, ob man gerade einen "fremden" Buffer benutzt und diesen erst noch kopieren muss.
    Und zweitens würde es völligen Murks geben, wenn man einen übergebenen char*-Buffer freigibt, während der String ihn noch referenziert.

    Speicherverbrauch ist hier auch nicht relevant. Zwar brauchen statische std::strings mehr Platz im RAM als Stringliterale (nicht nur durch ihre Struktur, sondern übrigens auch dadurch, dass zusätzlicher Code zur Erzeugung der Strings bei Programmstart erzeugt wird), aber in der Praxis macht das keinen Unterschied. Da müsste man schon tausende solche Strings haben.

    Der springende Punkt ist der zusätzliche Zeitaufwand für all die zusätzlichen static std::string... - Zeilen. Es ist eben sehr wohl zusätzliche Arbeit. Zwar stimmst du in einem Post zu, dass Dogmen schlecht sind und dass die Faustregel Sinn macht und dass man sein Hirn beim Coden nicht ausschalten sollte, aber im nächsten Post sagst du schon wieder, dass man sich angewöhnen sollte, die static std::string-Variante einfach immer ohne Nachdenken zu benutzen, also auch wenn die Faustregel nicht zutrifft und das kann es ja wohl nicht sein.
    Im Gegensatz dazu kostet Nachdenken übrigens gar nichts, da man es während dem Tippen macht.



  • NewSoftzzz schrieb:

    l'abra d'or schrieb:

    Im Binary wird das String-Literal mit in den Speicher geladen, und sobald der static const std::string erstellt wird wird dieses Literal dem Konstruktor übergeben, welcher das Chararray als Kopie zu den Membervariablen legt.

    Es gibt bestimmt einen const std::string Constructor, der einen Übergebenes c-string-literal als Buffer benutzt, und eben keinen eigenen anlegt? Wenn es den nicht geben würde, wäre das ziemliche Ressourcenverschwendung 😃

    mfg, René~

    Das würde vorraussetzen das std::strings immuteable sind, was aber nicht der Fall ist...


  • Mod

    loks schrieb:

    Das würde vorraussetzen das std::strings immuteable sind, was aber nicht der Fall ist...

    Kein Problem mit COW. Allerdings kann der string nicht wissen, ob das Argument selbst unveränderlich ist, noch kann das mitgeteilt werden.

    char foo[]="abcd";
    string bar = foo;
    foo[0]='e';
    // bar ist hier immer noch "abcd"
    

    Folglich muss stets eine Kopie angelegt werden. iirc hat Qts QString eine solche shallow-copy Funktionalität.



  • Wir reden aber von nem static const std::string 😉

    Der sollte doch immutable sein..

    mfg, René~



  • NewSoftzzz schrieb:

    Wir reden aber von nem static const std::string 😉

    Der sollte doch immutable sein..

    Das weiss der const char* Konstruktor aber immernoch nicht.


  • Mod

    Abgesehen vom WTF-Faktor, bekämpft NewSoftzzz ohnehin nur Symptome und nicht das Problem. Dient eine Stringklasse primär der Verwaltung von Zeichenketten, ist diese überflüssig, wenn wir mit Literalen hantieren. Die aufgerufene Funktion interessiert ja offenbar sowieso nicht, wie die Zeichenkette gespeichert wird. Folglich ist die aufzurufende Funktion zu ändern, und nicht beim Aufrufer zusätzlicher Unfug zu betreiben. z.B.

    void MyClass::parse(const char* begin, const char* end); // macht die eigentliche Arbeit
    // kann auch allgemeiner als Template mit geeigneten Iteratoren gestaltet werden
    
    void MyClass::parse(const char* str)
    {
        return parse( str, str + strlen( str ) );
    }
    template<size_t N>
    void MyClass::parse(const char (&str)[N])
    {
        return parse( str, str + N - 1 );
    }
    
    void MyClass::parse(const string& s)
    {
        return parse( s.data(), s.data() + s.size() );
    }
    

    Alles andere führt nur dazu, dass die aufzurufende Funktion unnötige Anforderungen an ihre Parameter stellt, was überflüssige Koppelung bedeutet. Und nur ganz nebenbei ist es ineffizient.



  • NewSoftzzz schrieb:

    Wir reden aber von nem static const std::string 😉

    Der sollte doch immutable sein..

    mfg, René~

    Ich bezog mich auf immutable Klasse, const dagegen erzeugt nur immutable Objekte. (Vergleiche String von C# oder Java, da sind die Klassen selbst immutable)

    std::string ist keine solche Klasse weil hier Änderungen immer auf den internen Buffer angewendet werden. (by design, schon klar) const kannste Du problemlos wegcasten (ebenfalls by design) und schon ist das Objekt wieder veränderbar:

    const std::string text = "hahahaha";
    const_cast<std::string*>(&text)->insert(0, "aber", 4);
    

    Auch mit const kannst Du also nicht verhindern das jemand bewußt den Inhalt des Objektes verändert, weil die Klasse std::string nicht immutable ist.


Anmelden zum Antworten