Set- und Getterfunktionen verwenden oder nicht?



  • Die verlinkte Klasse ist unter Umständen nicht ideal, da sie für Konvertierungen jeweils umkopiert. Sie ist darauf ausgelegt, dass man selbst einen Container mit Strings (in jenem Fall std::vector<std::string> ) besitzt, der selten einer Schnittstelle mit char** genügen muss.

    Allerdings habe ich in diesem Thread auch geschrieben, dass die Klasse bei Bedarf erweitert werden sollte. Sie bietet bereits ein Fundament, um Konvertierungen zu ermöglichen. Weitere Funktionalität muss man sich selbst implementieren, was aber aufgrund des unterliegenden STL-Containers nicht viel mehr Aufwand als ein Funktionsaufruf bedeutet.

    Das Wichtigste ist meiner Meinung nach – egal, was für eine Lösung du schlussendlich benutzt – dass fehleranfällige Low-Level-Operationen wie manuelle Speicherverwaltung innerhalb einer Klasse gekapselt sind, sodass der Benutzer sich wirklich wichtigen Dingen zuwenden kann.



  • Festzuhalten bleibt in jedem Fall, dass es Fälle gibt, in denen std::string nicht die ideale Lösung ist. Genau das sind die Fälle, in denen in der Praxis Pointer auf C-Strings oder schlechte Eigenbaulösungen verwendet werden.
    Oder vielleicht auch gute Eigenbaulösungen, der Vollständigkeit halber. Eigentlich kann es sowas kaum geben, weil man idealerweise ein std::string-kompatibles Interface haben möchte.



  • void MyClass::parse(const std::string& fileName)
    {
        std::ifstream stream( fileName.c_str() );
        ////
    }
    
    MyClass class;
    class.parse( "Hello.txt" );
    

    ist einer der Fälle, in denen man leicht auf std::string verzichten kann, denn es wird ein std::string-Objekt erzeugt, nur um auf den c_str zuzugreifen.
    Sobald aber die parse-Funktion den fileName nicht direkt verwendet, sondern z.B. in einem Daten-Verzeichnis nach einer Datei mit dem Namen sucht und zur Pfad-Zusammensetzung string-Operationen durchgeführt werden, kann sich der std::string schon wieder lohnen.

    Also, const char* nicht gleich verteufeln, es gibt (eben auch in der Standard-Bibliothek) genügend Verwendungsmöglichkeiten.



  • Ja, klar hier für das elleinige übergeben macht es nicht viel Sinn, aber bereits in C++0x wirds/sollte es dann auch Konstuktoren für std::string geben. Und dann ist es durchaus angenehmer, wenn man gleich einen std::string vorliegen hat, weil keine Umwandlungen mehr nötig sind.

    Klar gibt es Verwendungsmöglichkeiten für C-Strings, aber die sind nicht da, wo man dann einen std::string nachbaut.
    Obwohl.. Mir fällt kein richtig gutes Argument/Anwendungsgebiet wo C-Strings std::string wirklich überlegen sind ein, abgesehen von alten Schnittstellen.



  • SeppJ schrieb:

    Das ist 1 int mehr als bei char*. Dafür braucht's ein char weniger, weil die Nullterminierung entfällt.

    Nicht ganz. Das ist 1 size_t mehr, welches auf 64 bit Systemen einem uint64_t entspricht 😉

    Also 1 Byte vs 8 Byte.

    Wer allerdings bei Stringverkettungen nicht zuviel Performance verlieren möchte, sollte wie bei C Strings den benötigten Speicherbedarf errechnen/schätzen und dann via reserve() genügend reservieren.

    void MyClass::parse(const std::string& fileName)
    {
        std::ifstream stream( fileName.c_str() );
        ////
    }
    
    MyClass class;
    class.parse( "Hello.txt" );
    

    Oh je, solche Dinger sind ECHTE Performancefresser. Mit echte meine ich ECHTE.
    Bitte so schreiben:

    void MyClass::parse(const std::string& fileName)
    {
        std::ifstream stream( fileName.c_str() );
        ////
    }
    
    MyClass class;
    static const std::string halloString = "Hello.txt";
    class.parse( halloString );
    

    std::string Referenzen niemals mit statischen c-strings füttern 😉

    mfg~



  • > std::string Referenzen niemals mit statischen c-strings füttern 😉

    Und warum bitte?



  • NewSoftzzz schrieb:

    std::string Referenzen niemals mit statischen c-strings füttern 😉

    Naja. Entweder man lädt eine Datei einmal, und dann spielt es sicher keine Rolle, ob ein temporäres std::string -Objekt erstellt wird oder ein statisches. Oder man lädt häufig Dateien, wobei aber die Wahrscheinlichkeit gross ist, dass es sich um verschiedene handelt. Und dann ist man mit dem konstanten String wieder aufgeschmissen.

    Und also bitte, wenn es einem um Performance geht, darf man die Funktion ruhig einen const char* nehmen lassen. Das ist effizienter als beide Alternativen.

    NewSoftzzz schrieb:

    Oh je, solche Dinger sind ECHTE Performancefresser. Mit echte meine ich ECHTE.

    Wenn man eine Million mal pro Sekunde Dateien lädt, vielleicht.



  • Ad aCTa schrieb:

    > std::string Referenzen niemals mit statischen c-strings füttern 😉

    Und warum bitte?

    Weil bei jedem Aufruf der Funktion ein neues string Objekt erzeugt wird (und evtl. auch der c-string kopiert wird?)

    Auf jeden Fall ein Minibenchmark der beiden Varianten gegeinander hatte damals nen großen Performanceunterschied gezeigt.

    mfg~



  • theliquidwave schrieb:

    Noch einmal ausschweifend: Inline-Funktionen werden also vom Compiler direkt in den Code kompiliert? Dann müsste das hier zum Beispiel:

    inline CClass *CClassManager::getClass(char *pszName)
    {
        CClass *pClass = NULL;
        // iterieren, etc...
        return pClass;
    }
    
    CClass *pClass = pClassManager->getClass("aaa");
    

    zu folgendem werden (Compilerintern):

    CClass *pCompilerOptimizedClass = NULL;
    // iterieren, etc...
    CClass *pClass = pCompilerOptimizedClass;
    

    Hier sind ein paar wesentliche Stilfehler. Der Parameter sollte entweder vom typ const char* oder const std::string& sein. Ein char* impliziert ja, dass ich den String beschreiben kann. Allerdings ohne, dass ich die Länge kenne.

    Ausserdem sollte ein getter immer const deklariert werden und nie einen nicht-const Zeiger oder nicht-const Referenzen liefern. Besser sind sowieso Referenzen. Zeiger sollten soweit wie möglich vermieden werden.

    Zur ursprünglichen Frage möchte ich ergänzen, dass ein Getter und Setter keinen overhead liefern, wenn sie trivial in der Klasse oder inline im Header definiert sind. Dann werden sie grundsätzlich weg optimiert. Du wirst keinen C++-Compiler finden, der das nicht macht.



  • NewSoftzzz schrieb:

    Weil bei jedem Aufruf der Funktion ein neues string Objekt erzeugt wird (und evtl. auch der c-string kopiert wird?)

    Auf jeden Fall ein Minibenchmark der beiden Varianten gegeinander hatte damals nen großen Performanceunterschied gezeigt.

    Gehst du auf meinen Post auch ein oder lieber nicht? 😉



  • 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ß



  • Nexus schrieb:

    Gehst du auf meinen Post auch ein oder lieber nicht? 😉

    Ach, wenn es dir so wichtig ist :p

    Nexus schrieb:

    Und also bitte, wenn es einem um Performance geht, darf man die Funktion ruhig einen const char* nehmen lassen. Das ist effizienter als beide Alternativen.

    c-string -> std::string -> c-string.

    Klar, da kann man nen c-string nehmen und ist definitiv performanter (mach ich auch, wenn ich sowas in C Libraries aufrufe, z.B.). Davon hab ich aber gar nicht geredet. Ich meinte nur generell, jede Funktion, die eine std::string Referenz als Parameter hat, sollte man mit nem statischen std::string aufrufen.

    Allerdings gibt es da nen Unterschied, was die Effizienz angeht..

    Direkt nen c-String = 100% Performance
    static std::string -> c-string = 99% Performance (er hängt nur die terminierende '\0' an, sonst verbraucht das keine Ressourcen)
    c-string -> std::string -> c-string = 20% Performance (Ich glaube das hatte nen Faktor von 5 in meinem Benchmark, wenn bei jedem Funktionsaufruf ein std::string erzeugt wird und dabei wohl auch der c-string kopiert wird)

    mfg~



  • NewSoftzzz schrieb:

    Ich meinte nur generell, jede Funktion, die eine std::string Referenz als Parameter hat, sollte man mit nem statischen std::string aufrufen.

    "nur generell" ist eine nette Verniedlichung für eine Aussage, die es ganz schön in sich hat...
    Dir ist hoffentlich klar, was das bedeutet:
    Für jeden Aufruf muss ein statischer String mit komplett neuem Inhalt gefüttert werden. Da kann man ja schon fast einen neuen konstruieren.
    Synchronisation wird richtig lustig, wenn mehrere Threads gleichzeitig diese std::string-fressende Funktion mit dem statischen string füttern wollen. std::string ist sicherlich nicht synchronisiert, wodurch ganz amüsante, unvorhersehbare Sachen passieren können.
    Oder soll im Code an jeder Stelle, von der aus auf die Funktion zugegriffen wird, ein eigener statischer string liegen? ->Speicherverschwendung! Und den statischen string nach Verwendung leeren ist ja auch nicht grade das gelbe vom Ei.
    Ich denke das mit dem statischen String solle man in 99,9% der Fälle lieber lassen...



  • l'abra d'or schrieb:

    "nur generell" ist eine nette Verniedlichung für eine Aussage, die es ganz schön in sich hat...
    Dir ist hoffentlich klar, was das bedeutet:
    Für jeden Aufruf muss ein statischer String mit komplett neuem Inhalt gefüttert werden. Da kann man ja schon fast einen neuen konstruieren.
    Synchronisation wird richtig lustig, wenn mehrere Threads gleichzeitig diese std::string-fressende Funktion mit dem statischen string füttern wollen. std::string ist sicherlich nicht synchronisiert, wodurch ganz amüsante, unvorhersehbare Sachen passieren können.
    Oder soll im Code an jeder Stelle, von der aus auf die Funktion zugegriffen wird, ein eigener statischer string liegen? ->Speicherverschwendung! Und den statischen string nach Verwendung leeren ist ja auch nicht grade das gelbe vom Ei.
    Ich denke das mit dem statischen String solle man in 99,9% der Fälle lieber lassen...

    Ich versteh von oben bis unten nicht, wovon du redest? Statische Strings = stehen zur Compilezeit fest, werden bei Aufruf/Programmstart einmalig allokiert und bleiben zwischen allen Aufrufen der entsprechenden Methode persistent und konstant im Speicher. Es geht ja auch nur um "const std::string&" Referenzen in Methodenparametern. Und die sollte man definitiv mit "const static std::strings" aufrufen.

    mfg~



  • NewSoftzzz schrieb:

    Ich versteh von oben bis unten nicht, wovon du redest?

    Kein Grund für einen Full-Quote 😉

    Statische Strings = stehen zur Compilezeit fest, werden bei Aufruf/Programmstart einmalig allokiert und bleiben zwischen allen Aufrufen der entsprechenden Methode persistent und konstant im Speicher.

    static != const!
    Dass ein static-string zur Compilezeit feststeht wäre mir neu.
    Und das ist in meinen Augen Mist, denn die static-Variable wird erst bei Programmende wieder aus dem Speicher genommen -> Verschwendung.

    Es geht ja auch nur um "const std::string&" Referenzen in Methodenparametern. Und die sollte man definitiv mit "const static std::strings" aufrufen.

    Das heißt du gehst davon aus, dass eine Funktion immer nur mit dem gleichen Argument (zu mindest in einem bestimmten Kontext) aufgerufen wird?
    Finde ich arg limitierend was Interaktion mit der Software angeht...

    // edit:
    Und ich denke auch ein static const std::string ist nicht zur Compilezeit bekannt.
    In D gibt es Compiletime-Strings, finde ich ziemlich nett, vor allem bei Algorithmen anstelle einer Funktionsreferenz/Funktors.



  • l'abra d'or schrieb:

    Das heißt du gehst davon aus, dass eine Funktion immer nur mit dem gleichen Argument (zu mindest in einem bestimmten Kontext) aufgerufen wird?
    Finde ich arg limitierend was Interaktion mit der Software angeht...

    Hallo?? Hast du dir überhaupt mal den Kontext angeguckt?

    class.parse( "Hello.txt" );
    

    DARUM ging es die ganze Zeit. Das ist ja wohl ganz offensichtlich immer das gleiche Argument, oder? Und wenn parse nun einen "const std::string&" als Parameter hat, dann dort IMMER einen "static const std::string" benutzen. Jetzt klar?

    mfg, René~



  • NewSoftzzz schrieb:

    Hallo?? Hast du dir überhaupt mal den Kontext angeguckt?

    class.parse( "Hello.txt" );
    

    DARUM ging es die ganze Zeit. Das ist ja wohl ganz offensichtlich immer das gleiche Argument, oder? Und wenn parse nun einen "const std::string&" als Parameter hat, dann dort IMMER einen "static const std::string" benutzen. Jetzt klar?

    Ja klar, und ich wollte mit dem Codefetzen nur zeigen, dass die Übergabe eines std::string in dem Fall Mist ist, da ich da nur nen temporären std::string erzeuge, nur um dem ifstream einen std::string::c_str() zu geben.
    Eben weil in diesem Falle die verwendete Bibliothek (libstdc++) nur ein Interface für const char* anbietet und nicht für std::string.
    In jenem Kontext ist auch klar, dass es nicht darauf hinauslaufen wird, dass sehr oft immer wieder die selbe Datei geöffnet wird. Und in jenem Kontext ist auch nicht das Konstruieren eines temporären std::string-Objektes die Performancebremse, sondern es sind die Zugriffe auf die Festplatte.

    Ich verstehe dich durchaus, dass du bei Aufrufen, bei denen sich ABSOLUT NIEMALS das Argument ändert, ein static const std::string nützlich sein kann. Aber so viele Fälle sind das nicht, und wenn doch dann hast du ziemlich viele Strings im Speicher liegen.

    Und ich denke nicht, dass man beim Verzichten auf static const std::string jetzt so viel Ausführzeit spart. Wenn es Zeitkritisch wird, muss man sowieso alles vor dem kritischen Part konstruiert haben, denn Kopieren, Zuweisen, neu erstellen usw. bremst da schonmal gewaltig. Aber static const braucht man in den Bereichen dann trotzdem nicht (wirklich immer).

    Ich denke aber die Diskussion geht ziemlich am eigentlichen Anliegen des Posts vorbei, nämlich Sinn und Unsinn von gettern/settern.



  • l'abra d'or schrieb:

    Ich verstehe dich durchaus, dass du bei Aufrufen, bei denen sich ABSOLUT NIEMALS das Argument ändert, ein static const std::string nützlich sein kann. Aber so viele Fälle sind das nicht, und wenn doch dann hast du ziemlich viele Strings im Speicher liegen.

    Und ich denke nicht, dass man beim Verzichten auf static const std::string jetzt so viel Ausführzeit spart. Wenn es Zeitkritisch wird, muss man sowieso alles vor dem kritischen Part konstruiert haben, denn Kopieren, Zuweisen, neu erstellen usw. bremst da schonmal gewaltig. Aber static const braucht man in den Bereichen dann trotzdem nicht (wirklich immer).

    Ich hatte einen MMORPG Server programmiert, also eine Echtzeitapplikation. Dort gab es pro Sekunde ca. 10.000 - 100.000 solcher Methodenaufrufe und die Umstellung von c-strings auf static const std::strings hat was gebracht.

    l'abra d'or schrieb:

    Aber so viele Fälle sind das nicht, und wenn doch dann hast du ziemlich viele Strings im Speicher liegen.

    Ja, aber die liegen als C-Strings ja auch im Speicher rum. Übrigens sind das nicht unbedingt viele, sondern die werden einfach nur sehr sehr oft pro Sekunde aufgerufen (Bei einem MMORPG gibt es recht viele Textbausteine, die immer gleich sind usw.).

    Was ich hier erzähle ist also nicht irgendein Theoriekram, sondern das hab ich im harten praktischen Leben so gelernt.

    mfg, René~



  • Nur ganz kurz:
    Du hattest da also einen Spezialfall: Echtzeitanwendung! Also keine normale Standard0815-Anwendung, wo dieser "Oberhead" tatsächlich irrelevant sein sollte.
    Deine Textbausteine können wohl auch länger sein, also nicht ein Pfad/Name, der in 5-60 chars passt.
    In meinen Augen zu speziell um eine allgemeine Programmierregel daraus zu machen. Für deinen Fall ist sie aber durchaus sinnvoll.



  • l'abra d'or schrieb:

    In meinen Augen zu speziell um eine allgemeine Programmierregel daraus zu machen. Für deinen Fall ist sie aber durchaus sinnvoll.

    Warum daraus keine allgemeine Regel machen? Ich seh nämlich nur den Performancevorteil, aber keine Nachteile..

    Nenn mir einen Nachteil für diesen Fall?

    Also, wenn du keinen Nachteil nennen kannst, dann => Allgemeine Programmierregel (außer vllt. Embedded, weil ein std::string sizeof(size_t) mehr bytes braucht als der c-string).

    mfg, René


Anmelden zum Antworten