Stilfrage - pass per Value/const reference?



  • Mechanics schrieb:

    klassenmethode schrieb:

    Mechanics schrieb:

    Das weiß man meist auch nicht. Eine Funktion kann viele ifs haben und Unterfunktionen aufrufen. Manche Codepfade brauchen eine Kopie, andere nicht.

    wobei man eine einzelne Funktion, die mit if-Kaskaden in Bildschirmbreite gespickt ist und vor Unterfunktionsaufrufen strotzt, vielleicht erst einmal besser faktorisieren sollte.

    Du kannst überall reininterpretieren, was du willst. Und was ist, wenns nur ein if ist, reicht das nicht? Und sind Unterfunktionen kein Indiz dafür, dass die Funktion schon "besser faktorisiert" wurde und nicht alles selber macht?

    viele Unterfunktionsaufrufe innerhalb einer Funktion sind für mich ein Zeichen, daß man noch weiter faktorisieren sollte.

    Aber mal allgemein: Ich finde schon, daß es guter Stil ist, wenn man einer Funktion auf Anhieb ansieht, ob für einen Argumenttyp irgendwo weiter drin z.B. ein copy constructor verwendet wird. const& als Argumenttyp ist für mich schon ein Indiz, daß ich mir üblicherweise keine Gedanken darüber machen muß.

    Aber meinetwegen kann natürlich jeder programmieren wie er will, Punkt. Ach nein, besser: Komma. Solange ich seinen Code nicht debuggen muß 😃



  • klassenmethode schrieb:

    viele Unterfunktionsaufrufe innerhalb einer Funktion sind für mich ein Zeichen, daß man noch weiter faktorisieren sollte.

    und weiter 'faktorisieren' heißt bei dir konkret was? Etwa funktionsaufrufe eliminieren und durch den funktionsinhalt ersetzen?!



  • na zum Beispiel so was:

    f();
    do_something();
    g();
    do_something_else();
    h() && i();
    

    ersetzen durch:

    f();
    do_some_things();
    
    void do_some_things(){
      do_something();
      g();
      k();
    }
    
    void k(){ 
      do_something_else();
      h() && i(); 
    }
    


  • Zur sauberen Schnittstelle:
    Für mich sind call-by-value und call-by-const-reference gleich sauber, beide haben keine Seiteneffekte (zumindest für den übergebenen Parameter). Und wenn jemand sagt, dass call-by-const-reference Aussagen über die Funktionsweise der Funktion macht, dann gilt das für call-by-value auch.

    Ganz konkret extrahiert die Funktion den Pfadanteil eines Dateipfades unter Windows. Weil Windows sowohl den Slash als auch den Backslash als Pfadtrennzeichen akzeptiert werden vorher alle Slashes durch Backslashes ersetzt. Das erfordert, dass irgendein string veränderbar sein muss. Um den übergebenen String zu bearbeiten gibt es jetzt die beiden Möglichkeiten, die ich oben angeboten habe.

    Ich bin für Variante 1, weil der Compiler beim pass-by-value entscheiden kann, ob und wann er eine Kopie anlegt oder ob er die Änderungen direkt in Zielvariable durchführt.

    string s = f( "c:\\path/path1\\path2/file.txt" );
    

    Variante 2 sieht für mich unnatürlich aus, weil offensichtlich klar ist, dass man mit dem per const-reference übergebenen Parameter nix anfangen kann und erst ein Mal ein Kopie erzeugen muss. Da ist die Schnittstelle eher im Weg, weil sie einen unpassenden Parametertyp anbietet.



  • Nur paar Überlegungen zu deinem Beispiel...

    1. Wenn du das in eine Basisbibliothek packst und eine Dll draus baust, kannst du vergessen, dass der Compiler irgendwas optimiert.
    2. Vielleicht wärs eine Optimierung, erstmal zu schauen, ob man überhaupt was ersetzen muss, bevor man eine Kopie macht. Drüberlaufen und schauen, ob ein Slash vorkommt, ist evtl. schneller, als jedesmal gleich eine Kopie zu erstellen. Wenn du gleich eine Kopie reinbekommst, geht die Optimierung schon mal nicht.
    3. Ein Kollege wird über den Code drüber schauen und sich denken, warum zum Geier wird da eine Kopie gemacht und was ersetzt, ich schau einfach beim Scannen, obs Slashes oder Backslashes sind und brauch überhaupt keine Kopie und keine Ersetzung. Passt aber die Schnittstelle nicht an.



  • Mechanics schrieb:

    1. Wenn du das in eine Basisbibliothek packst und eine Dll draus baust, kannst du vergessen, dass der Compiler irgendwas optimiert.

    Das stimmt zwar für Compiler-Optimierungen wie RVO, nicht jedoch für das Argument mit dem Move-Konstruktor.
    Ein "move" ist schliesslich eigentlich keine Compiler-Optimierung, sondern eine explizite Optimierung im Code der Klasse.
    Auch wenn f() eine DLL-Funktion ist, wird hier beim Aufruf von f("abc") eine Kopie gespart:

    string f(string s)
    {
       transform(s); // Keine Kopie: es wird direkt auf s gearbeitet.
       return s;     // 1. Move: Auch wenn hier bei einer DLL-Funktion kein NRVO (s ist alias für result) stattfinden kann,
                     //          so kann und wird der Compiler im Allgemeinen hier ein "return std::move(s)" generieren.
    }
    
    auto result = f("abc"); // 1. Kopie: char* wird im Konstruktor in std::string kopiert.
                            // 2. Move: Zuweisung nach result.
    

    Also: 1 Kopie, 2 Moves.

    vs.

    string f(const string& s)
    {
       string t = s; // 2. Kopie: von einer const& kann man nicht moven.
       transform(t); // s.o.
       return t;     // 1. Move: s.o.
    }
    
    auto result = f("abc"); // 1. Kopie: char* wird im Konstruktor in std::string kopiert.
                            // 2. Move: Zuweisung nach result.
    

    2 Kopien, 2 Moves. In diesem Fall fährt man mit "by value" besser. Auch in einer DLL-Funktion.

    Mechanics schrieb:

    2. Vielleicht wärs eine Optimierung, erstmal zu schauen, ob man überhaupt was ersetzen muss, bevor man eine Kopie macht. Drüberlaufen und schauen, ob ein Slash vorkommt, ist evtl. schneller, als jedesmal gleich eine Kopie zu erstellen. Wenn du gleich eine Kopie reinbekommst, geht die Optimierung schon mal nicht.

    Das sowieso. Der schnellste Code ist immer noch der, der nicht ausgeführt werden muss. In so einem Fall macht const& Sinn.

    Mechanics schrieb:

    3. Ein Kollege wird über den Code drüber schauen und sich denken, warum zum Geier wird da eine Kopie gemacht und was ersetzt, ich schau einfach beim Scannen, obs Slashes oder Backslashes sind und brauch überhaupt keine Kopie und keine Ersetzung. Passt aber die Schnittstelle nicht an.

    Das ist aber ein sehr pessimistiches Argument. Ich soll Code schreiben, von dem ich weiss, dass er für meine Lösung ineffizienter ist, nur weil er für eine andere hypothetische Lösung besser ist?
    Ich halte es für wahrscheinlicher dass entweder mein Code Sinn macht, oder mein Kollege das ordentlich umsetzt. Dass es alle Beteiligten verbocken würde ich als Sonderfall betrachten,
    für den man nicht unbedingt optimieren sollte (obwohl - wenn man sich so manchen Code aus der freien Wirtschaft so ansieht könnte es durchaus Sinn machen für solche Fälle zu programmieren :D)

    Finnegan



  • @Mechanics:
    Zu gucken, ob überhaupt eine Kopie gemacht werden muss, halte ich in diesem Fall für übertrieben. Wenn man das letzte Fitzelchen Performance braucht kann man das machen, aber ich bezweifle, dass diese Funktion ein Flaschenhals sein wird.
    Und, Hand auf´s Herz, wenn du eine to_lower_case Funktion schreibst guckst du auch nicht erst, ob überhaupt Großbuchstaben im string vorhanden sind, oder?



  • Ich habe nicht alles gelesen. Aber ist pass by const reference nicht immer schneller als pass by value, außer wenn der value in einen size_t passt?



  • DocShoe schrieb:

    Edit 2:
    Ich habe mich noch nicht ausgiebig mit C++11 beschäftigt, kann ich den Rückgabeparameter als rvalue referenzen und std::move zurückgeben?

    std::string&& f( std::string s )
    {
       return std::move( s );
    }
    

    ...

    Nein, das ist falsch. Du gibst hier eine Referenz auf ein temporäres Objekt zurück, was schon nicht mehr da ist, wenn der Aufrufer die Referenz bekommt. Da macht die Art der Referenz gar keinen Unterschied. So oder so ist das eine baumelnde Referenz.

    Die Magie bei Move-Semantik steckt nicht in den Rvalue-Referenzen oder im std::move . Sie sitzt im Move-Constructor und im Move-Assignment-Operator der Klasse. Dort wird kontrolliert, was ein "move" bedeutet. Das kann der Klassenautor selbst festlegen.

    Man sollte nie "return x;" durch "return std::move(x);" ersetzen, wenn x ein funktionslokales Objekt ist. Damit würde man nämlich die return-value-Optimierung (RVO) aushebeln. Und falls der Compiler keine RVO durchführen kann, ist er dazu verpflichtet, x wie ein Rvalue zu behandeln. Man muss sich da also keine Sorgen machen, dass da etwas unnötig kopiert wird.



  • Zusammenfassung schrieb:

    Ich habe nicht alles gelesen. Aber ist pass by const reference nicht immer schneller als pass by value, außer wenn der value in einen size_t passt?

    OK, ich habe nicht genau gelesen. Der Übergabewert wird evtl. verändert.


Anmelden zum Antworten