Wann by Pointer und wann by Reference



  • DocShoe schrieb:

    SeppJ schrieb:

    Pointer nur, wenn es irgendeinen Grund geben könnte, dass dieser Null ist. Was so gut wie nie der Fall sein wird.
    ...

    Aha...
    Woher nimmst du diese Erkenntnis?

    Eigene Erfahrung?



  • DocShoe schrieb:

    SeppJ schrieb:

    Pointer nur, wenn es irgendeinen Grund geben könnte, dass dieser Null ist. Was so gut wie nie der Fall sein wird.
    ...

    Aha...
    Woher nimmst du diese Erkenntnis?

    ich sehe das auch gleich wie SeppJ.



  • ;Han schrieb:

    Bonus: Bei Ausgabeparametern bevorzugen manche Leute call by pointer, auch wenn der Parameter nicht optional ist.
    Grund ist, dass es nicht immer offensichtlich ist, dass ein Aufruf wie foo(a,b,c) einige der übergebenen Objekte verändert.
    Bei foo(&a,&b,&c) rechnet man schon eher damit.

    Schonmal von C++11 gehört? Wenn man das wirklich anzeigen will, dann bitte richtig:

    foo(std::ref(a),std::ref(b),std::ref(c));
    

    ;Han schrieb:

    Call by pointer, wenn der Parameter optional ist (statt einen Zeiger auf ein Objekt kannst du auch nullptr übergeben).

    null considered harmful. Nullpointer lassen sich durch ein Null-State beheben. Nullstring -> "". Nullvector -> std::vector<int>(). Null übergeben zu dürfen impliziert, dass irgendwo auf NULL abgefragt werden muss, und das ist langsam und umständlich und verwirrend. Wenn sich eine Funktion anders verhalten sollte, abhängig davon ob der eine Parameter gegeben oder nicht gegeben ist, dann sind das in Wirklichkeit zwei Funktionen.

    Siehe Boost.Optional. Von den 4 Beispielen handelt keines von optionalen Parametern.



  • reffreffreff schrieb:

    Schonmal von C++11 gehört? Wenn man das wirklich anzeigen will, dann bitte richtig:

    foo(std::ref(a),std::ref(b),std::ref(c));
    

    Nö, std::ref war mir nur im Zusammenhang mit bind und ähnlichem bekannt, aber das ist ein schönes Einsatzgebiet.

    reffreffreff schrieb:

    Nullpointer lassen sich durch ein Null-State beheben. Nullstring -> "". Nullvector -> std::vector<int>().

    Ich sorge zwar ausnahmslos dafür, dass meine Klassen, die im weitesten Sinne einen Satz von Daten repräsentieren, einen Nullstate haben (inklusive explicit operator bool() , welcher auf diesen testet), hätte aber nicht erwartet, dass sowas Praxis ist.

    reffreffreff schrieb:

    Wenn sich eine Funktion anders verhalten sollte, abhängig davon ob der eine Parameter gegeben oder nicht gegeben ist, dann sind das in Wirklichkeit zwei Funktionen.

    Da magst du wohl Recht haben. Das passiert bei mir zwar ohnehin nur alle tausend Funktionen einmal, aber beim nächsten Mal werde ich das unter diesem Gesichtspunkt nochmal kritisch beurteilen.



  • reffreffreff schrieb:

    Schonmal von C++11 gehört? Wenn man das wirklich anzeigen will, dann bitte richtig:

    foo(std::ref(a),std::ref(b),std::ref(c));
    

    Weisst du auch, was für einen Typ std::ref() zurückgibt? Tipp: Es ist keine Referenz.

    reffreffreff schrieb:

    Null übergeben zu dürfen impliziert, dass irgendwo auf NULL abgefragt werden muss, und das ist langsam und umständlich und verwirrend. Wenn sich eine Funktion anders verhalten sollte, abhängig davon ob der eine Parameter gegeben oder nicht gegeben ist, dann sind das in Wirklichkeit zwei Funktionen.

    Beispiel: Ein Raumschiff kann ein Ziel anvisiert haben oder nicht, set/getTarget() operieren mit Zeigern, die möglicherweise Null sind. Naheliegendste Version:

    ship.setTarget(otherShip.getTarget());
    

    Dogmatische Version mit "in Wirklichkeit zwei Funktionen":

    if (otherShip.hasTarget())
        ship.setTarget(otherShip.getTarget());
    else
        ship.resetTarget();
    

    Resultat: 400% Code (+2 zusätzliche Memberfunktionen), braucht viel länger bis die Semantik "weise meinem Schiff das Ziel des anderen Schiffs zu" verstanden ist, ausserdem eine unnötige und langsame Fallunterscheidung. Deine Vorgehensweise kann recht nach hinten losgehen.

    ;Han schrieb:

    Ich sorge zwar ausnahmslos dafür, dass meine Klassen, die im weitesten Sinne einen Satz von Daten repräsentieren, [...], hätte aber nicht erwartet, dass sowas Praxis ist.

    Ist es auch nicht. Denn das verschiebt das Problem nur: Statt den Zeiger auf Gültigkeit zu testen, testest du das Objekt auf Gültigkeit. Gut, in manchen Fällen wie leeren Containern klappt das implizit, weil die Iterations-Schleife einfach nichts tut. Diese Fälle würde ich aber nicht speziell als "Null-State" bezeichnen.

    In anderen Fällen kannst du den Code durch in Klassen eingebaute Null-States jedoch verschlimmern. Du verletzt damit das Prinzip von Invarianten und musst Objekte vor ihrer Verwendung auf Gültigkeit prüfen. Daher lieber ein boost::optional ( std::optional kommt ja leider nicht für C++14) drum herum packen, um die Optionalität explizit kennzuzeichnen.



  • Nexus schrieb:

    reffreffreff schrieb:

    Schonmal von C++11 gehört? Wenn man das wirklich anzeigen will, dann bitte richtig:

    foo(std::ref(a),std::ref(b),std::ref(c));
    

    Weisst du auch, was für einen Typ std::ref() zurückgibt? Tipp: Es ist keine Referenz.

    Es ging genau darum, die unsicheren (potentiell nullen) Pointer durch die sichereren Reference-Wrapper zu ersetzen.

    Beispiel: Ein Raumschiff kann ein Ziel anvisiert haben oder nicht, set/getTarget() operieren mit Zeigern, die möglicherweise Null sind. Naheliegendste Version

    Damit ist der Fall doch klar:

    class target {
      ship *target;
    public:
      target(ship& other_ship) : target(&other_ship) {}
      target() : target(0) {}
    
      operator bool() const { return target && !target->stealthed(); }
      ship& get() { assert(valid()); return *target; }
    };
    
    ship.target() = otherShip.target();
    

    Resultat: 50% kürzerer Code (+ 1 Memberfunktion weniger). Dazu sicherer und flexibler.



  • Nexus schrieb:

    ;Han schrieb:

    Ich sorge zwar ausnahmslos dafür, dass meine Klassen, die im weitesten Sinne einen Satz von Daten repräsentieren, [...], hätte aber nicht erwartet, dass sowas Praxis ist.

    Ist es auch nicht. Denn das verschiebt das Problem nur: Statt den Zeiger auf Gültigkeit zu testen, testest du das Objekt auf Gültigkeit. Gut, in manchen Fällen wie leeren Containern klappt das implizit, weil die Iterations-Schleife einfach nichts tut. Diese Fälle würde ich aber nicht speziell als "Null-State" bezeichnen.

    Ich verstehe Null-State als State, der zwar valide ist, aber nichts tut (ist manchmal nicht zu vermeiden -- siehe dein Target-Beispiel).

    In anderen Fällen kannst du den Code durch in Klassen eingebaute Null-States jedoch verschlimmern. Du verletzt damit das Prinzip von Invarianten und musst Objekte vor ihrer Verwendung auf Gültigkeit prüfen. Daher lieber ein boost::optional ( std::optional kommt ja leider nicht für C++14) drum herum packen, um die Optionalität explizit kennzuzeichnen.

    Das ist für mich kein guter Null-State mehr. Der Punkt ist, dass es Null-States quasi sowieso braucht, wenn man den Move-Konstruktor definiert. Und ungültige Zustände sollte man eh nur haben, wenn das so im Design gewünscht ist (dein Target). Null-State kann auch kostspielig sein, siehe std::list, deren Null-State dynamischen Speicher besitzt.



  • reffreffreff schrieb:

    Es ging genau darum, die unsicheren (potentiell nullen) Pointer durch die sichereren Reference-Wrapper zu ersetzen.

    Ich glaube, du hast Reference-Wrapper falsch verstanden: Sie sind keine Referenzen, und niemand verwendet sie als Parametertyp. Sie existieren, um Referenzen zu binden, weil man in einem Kontext wie std::bind() keine andere Möglichkeit hat, eine Referenz zu übergeben. std::reference_wrapper ist nicht zur direkten Übergabe konzipiert.

    reffreffreff schrieb:

    ship.target() = otherShip.target();
    

    Resultat: 50% kürzerer Code (+ 1 Memberfunktion weniger). Dazu sicherer und flexibler.

    Was gibt target() zurück? Eine Referenz? Damit verlierst du die Kapselung. Der Code ist übrigens gleich lang (je 1 Zeile).

    Ausserdem ist eine eigene Klasse für Ziele Overkill. Wenn du andere Verweise hast, willst du jeweils eigene Klassen erstellen? Für Eskorten, Leader, und alle möglichen Beziehungen zwischen Raumschiffen? Ne, die Zeiger-Lösung funktioniert und ist einfach.



  • Ich sehe zwar prinzipiell worauf du hinaus willst Nexus, und sehe es nicht als unplausibel, aber warum nicht

    ship.UseTargetFrom(otherShip)
    

    ?
    Ist diese Funktion was triviales with target=othership.target; dürfte das auch keinerlei Laufzeitunterschiede haben.

    Meiner Meinung nach wird dadurch nicht nur klarer was man eigentlich will, man kann eben auch unterschiedliches Verhalten beim übernehmen eines Ziels und beim setzen eines Ziels einführen. Ich weiß von Freespace 2, dass die "Ziel des Ziels übernehmen" Funktion sowas nicht gemacht hat, was zu dem ungewollten Verhalten geführt hat, Dinge ins Ziel nehmen zu können die man eigentlich nicht ins Ziel nehmen können sollte - indem man einfach die KI-Schiffe genutzt hat (bei denen ist es gewissermaßen notwendig, dass sie sich noch immer ein Ziel wählen können)

    Solche Get/Set Konstrukte sind mir aber unabhängig davon nie ganz geheuer...



  • Ich arbeite generell nur mit Pointern, das habe ich noch aus den C-Zeiten. Ich bin es einfach über die Jahre gewöhnt und habe auch noch nie Probleme damit. Mein C++ ist eher ein C mit Klassen und das wird sich wohl auch nicht mehr ändern, da es einfach für mich gut so funktioniert.



  • Zeigmal schrieb:

    IIch bin es einfach über die Jahre gewöhnt und habe auch noch nie Probleme damit. Mein C++ ist eher ein C mit Klassen und das wird sich wohl auch nicht mehr ändern, da es einfach für mich gut so funktioniert.

    Gleich kommt der Shitstorm.



  • Jetzt werden sie mich wieder hauen...

    Pointer wenn ich den ganzen OOP-kram umgehen und auf ein Objekt direkt zugreifen will.
    Ich weiss, daß das mies im Sinne der anständigen Programmierung ist. Ich mach's trotzdem.



  • Kann doch jeder programmieren wie er will. Die einen machen sich halt hundert Gedanken vorher und halten jede Regel ein, versuchen dazu immer ein DP mit rein zu quetschen und den denken dann noch nach wie sie den Code zu Tode generalisieren könnten und die anderen sind in der Zeit schon fast fertig.



  • 1312 schrieb:

    Kann doch jeder programmieren wie er will. Die einen machen sich halt hundert Gedanken vorher und halten jede Regel ein, versuchen dazu immer ein DP mit rein zu quetschen und den denken dann noch nach wie sie den Code zu Tode generalisieren könnten und die anderen sind in der Zeit schon fast fertig.

    DP - ist das das? http://de.wikipedia.org/wiki/Doppelpenetration



  • Ich denke er meint Design Patterns.



  • 1312 schrieb:

    Kann doch jeder programmieren wie er will. Die einen machen sich halt hundert Gedanken vorher und halten jede Regel ein, versuchen dazu immer ein DP mit rein zu quetschen und den denken dann noch nach wie sie den Code zu Tode generalisieren könnten und die anderen sind in der Zeit schon fast fertig.

    In professioneller Umgebung schreibt man Code nicht fuer sich allein. Dort kann nicht jeder programmieren wie er will.



  • 1312 schrieb:

    Kann doch jeder programmieren wie er will. Die einen machen sich halt hundert Gedanken vorher und halten jede Regel ein, versuchen dazu immer ein DP mit rein zu quetschen und den denken dann noch nach wie sie den Code zu Tode generalisieren könnten und die anderen sind in der Zeit schon fast fertig.

    Und während die einen Bugs innerhalb von Stunden fixen und neue Features innerhalb von einigen Tagen einbauen, blickt bei den anderen nach einem halben Jahr keiner mehr durch den Code durch.



  • daddy_felix schrieb:

    Und während die einen Bugs innerhalb von Stunden fixen und neue Features innerhalb von einigen Tagen einbauen, blickt bei den anderen nach einem halben Jahr keiner mehr durch den Code durch.

    Und besonders schön ist, wenn man Code von Anderen übernehmen muss, und man vorgeworfen bekommt wie langsam man doch sei (Kryptischer Code, 5fach Zeiger, keine Kommentare und bei Fragen an den betreffenden Entwickler: "Schau in den Code"). Besonders schön ist auch wenn man Variablen undeutlich bezeichnet, und die von drei Codestellen unterschiedlich gesetzt werden (Dem Entwickler ist zwar bekannt das globale Variablen nur mit Bedacht eingesetzt werden sollen, sein Ersatz waren dann aber beispielsweise 50 public static Member in einer Sammelklasse...).

    Grundsätzlich sollte man sich einen sauberen Code angewöhnen, auch wenn man dies unter Zeitdruck etc. nicht immer hin bekommt. Wenn man im Stil von "1312" programmiert kann man für sich im eigenen Kämmerchen entwickeln, aber man sollte die Finger von Projekten lassen bei denen mehr als eine Person arbeitet.



  • Und wie genau übertragen sich die hier genannten Gruselszenarien auf die Frage, ob man Referenzen oder Zeiger verwenden sollte?



  • SeppJ schrieb:

    Pointer nur, wenn es irgendeinen Grund geben könnte, dass dieser Null ist. Was so gut wie nie der Fall sein wird. Daher: Referenz, außer es gibt einen sehr guten Grund dagegen.

    Das ist die Sicht des Aufgerufenen.

    Aus Sicht des Aufrufers
    "Pointer, wenn ich erlaube, daß der Aufgerufene was ändert. Const-Ref oder Kopie, wenn ich es nicht erlaube." Ich sehe im Code des Aufrufers am & immer sofort, daß da was heikel ist.

    Beim Aufrufer mache ich Fehlerfreiheit. Der Aufgerufene ist schon fehlerfrei.


Anmelden zum Antworten