Wann by Pointer und wann by Reference



  • Hallo,

    es geht um die beiden Funktionen oben. Wann sollte man was benutzen und wo ist genau der Unterschied? Irgendwie verstehe ich das noch nicht so genau:-(

    #include <string>
    #include <vector>
    #include <Windows.h>
    using namespace std;
    
    int fillvector1(int i, vector<string> *vec) //Call by Pointer
    {
    	for (int j = 0; j <= i; ++j)
    	{
    		vec->push_back("Hallo welt1");
    	}
    	return vec->size();
    }
    int fillvector2(int i, vector<string> &vec) //Call by Reference
    {
    	for (int j = 0; j <= i; ++j)
    	{
    		vec.push_back("Hallo welt2");
    	}
    	return vec.size();
    }
    int fillvector3(int i, vector<string>  vec) //Call by Value
    {
    	for (int j = 0; j <= i; ++j)
    	{
    		vec.push_back("Hallo welt3");
    	}
    	return vec.size();
    }
    
    int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    {
    	vector<string> *strvec1 = new vector<string>();
    	vector<string> *strvec2 = new vector<string>();
    	vector<string>  strvec3;
    
    	int size1 = fillvector1(1001,  strvec1);
    	int size2 = fillvector2(1002, *strvec2);
    	int size3 = fillvector3(1003,  strvec3);
    
    	MessageBox(NULL, strvec1->at(0).c_str(), "Test1", MB_OK); //Geht
    	MessageBox(NULL, strvec2->at(0).c_str(), "Test2", MB_OK); //Geht
    	MessageBox(NULL, strvec3. at(0).c_str(), "Test3", MB_OK); //Geht nicht weil Inhalt nur in Funktion präsent
    }
    

  • Mod

    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.



  • Call by value, wenn du in der Funktion sowieso eine Kopie des übergebenen Objekts gemacht hättest und das Original nicht verändern willst.
    Call by pointer, wenn der Parameter optional ist (statt einen Zeiger auf ein Objekt kannst du auch nullptr übergeben).
    Call by reference andernfalls.

    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.



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



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


Anmelden zum Antworten