Call by Value, Call by Reference, und dann... ?



  • icarus2 schrieb:

    mgaeckler schrieb:

    Aus diesem Grund verwende ich bei Funktionsdefinitionen, wenn ich call by reference brauche eher selten Referenzen sondern lieber Zeiger.

    Und dann hast du das Problem, dass Zeiger nullptr sein können. Ist eine gute Quelle für UB... 🙄

    Bei welcher Gelegenheit liefert der Adressoperator eine NULL? Nicht initialisierte Zeiger sind ein Problem, das hat aber erst mal nix mit Zeigern als Funktionsparameter zu tun. Außerdem manchmal will man ja eine NULL zurückliefern oder übergeben, um mitzuteilen "Sorry hab gerade nix". Das geht mit Referenzen gar nicht.

    mfg Martin


  • Mod

    Bei welcher Gelegenheit liefert der Adressoperator eine NULL?

    *patsch*
    Du bist zu gutgläubig.

    Außerdem manchmal will man ja eine NULL zurückliefern oder übergeben, um mitzuteilen "Sorry hab gerade nix".

    Genau, und wenn nicht nehmen wir trotzdem Zeiger. Einfach so. Weil wir fehleranfällige Programme fördern, sonst hat dieses Forum ja keinen Daseinszweck.

    Dein Argument mit der & -Syntax, dass auf der call-site klarer wird was mit dem Argument geschieht, ist relativ schwach. Der Programmierer muss wissen was eine Funktion tut um einen Aufruf derer zu verstehen. Dementsprechend muss er auch die Funktionssignatur kennen. Und wenn er sie kennt, braucht er keinen Hinweis darauf dass der Parameter eine Referenz ist.

    Rohe Zeiger als Funktionsparameter sind in C++ IMO fast nur für Arrays sinnvoll. Für optionale Parameter gibt es Überladung. Und optionale Rückgabetypen sind std::optional (bald im TR) oder unique_ptr .

    Referenzen zwar auch, aber einige Fehler lassen sich damit vermeiden.

    Nein, Referenzen haben ein sehr kleines Fehlerpotential. Nur hängende Referenzen stellen ein Problem dar.



  • icarus2 schrieb:

    //- call by reference
    void functions2(int* b)
    

    ist nicht Call by Reference. Der Pointer (den man als Argument übergibt) wird kopiert, d.h. es handelt sich um Call by Value.

    C und Java unterstützen kein Call by Reference. Man kann zwar einen Pointer (in C) oder eine Referenz (in Java) übergeben aber diese werden stets kopiert.

    Das ist aber sehr spitzfindig und für die Praxis völlig ohne Bedeutung. K & R haben übrigens über diesen Umstand ein eigenes kleines Kapitel. Wer in C von call by reference sprach, hat immer POINTER als Formalparameter gemeint, in C++ können es halt AUCH REFERENZEN sein.

    mfg Martin



  • Arcoth schrieb:

    Dein Argument mit der & -Syntax, dass auf der call-site klarer wird was mit dem Argument geschieht, ist relativ schwach.

    ne

    Arcoth schrieb:

    Der Programmierer muss wissen was eine Funktion tut um einen Aufruf derer zu verstehen.

    wieder ne

    Arcoth schrieb:

    Dementsprechend muss er auch die Funktionssignatur kennen. Und wenn er sie kennt, braucht er keinen Hinweis darauf dass der Parameter eine Referenz ist.

    blah

    Arcoth schrieb:

    Rohe Zeiger als Funktionsparameter sind in C++ IMO fast nur für Arrays sinnvoll.

    schon wieder bullshit

    Arcoth schrieb:

    Für optionale Parameter gibt es Überladung.

    quatsch

    Arcoth schrieb:

    Und optionale Rückgabetypen sind std::optional (bald im TR) oder unique_ptr .

    nope, auch nich

    Arcoth schrieb:

    Nein, Referenzen haben ein sehr kleines Fehlerpotential.

    und das ganz sicher nich

    Arcoth schrieb:

    Nur hängende Referenzen stellen ein Problem dar.

    ok



  • Arcoth schrieb:

    Bei welcher Gelegenheit liefert der Adressoperator eine NULL?

    *patsch*
    Du bist zu gutgläubig.

    Das beantwortet nicht meine Frage.

    Arcoth schrieb:

    Außerdem manchmal will man ja eine NULL zurückliefern oder übergeben, um mitzuteilen "Sorry hab gerade nix".

    Genau, und wenn nicht nehmen wir trotzdem Zeiger. Einfach so. Weil wir fehleranfällige Programme fördern, sonst hat dieses Forum ja keinen Daseinszweck.

    Also in meiner knapp 30 jährigen Praxis kamen mir nicht initialisierte Zeiger eher selten vor. "Vagabundierene" Zeiger sind eher ein Problem, das geht aber eben auch mit Referenzen.

    Jetzt aber mal ein Beispiel, das ich mit Referenzen nicht (oder langsamer) lösen kann:
    Ich habe ein Baum und brauche eine Funktion, die mir einen Knoten zurückliefert, der eine bestimmte Bedingung erfüllt. Was soll die Funktion zurückgeben, wenn es einen solchen Knoten nicht gibt? NULLREFERENCE gibt es ja nicht. Eine Exception zu werfen ist ja auch nicht das gelbe vom Ei, weil das auf die Performanz geht. Dem Aufrufer ist es aber ein leichtes zu überprüfen, ob er eine NULL zurückbekommen hat.

    Arcoth schrieb:

    Dein Argument mit der & -Syntax, dass auf der call-site klarer wird was mit dem Argument geschieht, ist relativ schwach. Der Programmierer muss wissen was eine Funktion tut um einen Aufruf derer zu verstehen. Dementsprechend muss er auch die Funktionssignatur kennen. Und wenn er sie kennt, braucht er keinen Hinweis darauf dass der Parameter eine Referenz ist.

    Grundätzlich gebe ich Dir recht, aber wenn man sich neu in ein Projekt einarbeiten muß, ist das schon sehr hilfreich.

    Arcoth schrieb:

    Rohe Zeiger als Funktionsparameter sind in C++ IMO fast nur für Arrays sinnvoll. Für optionale Parameter gibt es Überladung. Und optionale Rückgabetypen sind std::optional (bald im TR) oder unique_ptr .

    Überladung ist war manchmal (vieleicht auch oft) auch eine sinnvolle Lösung, aber leider nicht immer die beste. Beispiel:

    Eine Funktion macht einen Abgleich des lokalen Dateisysters mit der Datenbank. Die Funktion kann vom Anwender direkt über ein Menüpunkt aufgerufen werden oder aber von einem Hintergrundprozess. Im letzteren Fall übergebe ich der Funktion ein Dateihandle mit, in der sie ein Protokoll schreiben kann im ersten Fall ist das nicht notwendig:

    bool THE_FOLDER_REF::refresh( bool recursive, ostream *stream )
    {
    	....
    	if( stream )
    		*stream << "Folder Created: " << newFolder->getPath() << '\n';
    	....
    }
    

    Nur wegen zwei Zeilen zwei Version einer 240 Zeilen langen Funktion?

    Merke: Zeiger sind gut beherschbar, wenn man die möglichen Probleme kennt und diese bei der Implementierung im Hinterkopf behält. Für komplexere Angelegenheiten sollte man natürlich irgendwelche Smart-Pointer benutzen.

    Arcoth schrieb:

    Referenzen zwar auch, aber einige Fehler lassen sich damit vermeiden.

    Nein, Referenzen haben ein sehr kleines Fehlerpotential. Nur hängende Referenzen stellen ein Problem dar.

    Das würde ich eben nicht als kleines Fehlerpotential betrachten. Das kann ziemlich gemein sein. Das geht schneller als man denkt.

    mfg Martin



  • Kellerautomat schrieb:

    Arcoth schrieb:

    Dein Argument mit der & -Syntax, dass auf der call-site klarer wird was mit dem Argument geschieht, ist relativ schwach.

    ne

    doch

    Arcoth schrieb:

    Der Programmierer muss wissen was eine Funktion tut um einen Aufruf derer zu verstehen.

    wieder ne

    wieder doch

    Arcoth schrieb:

    Dementsprechend muss er auch die Funktionssignatur kennen. Und wenn er sie kennt, braucht er keinen Hinweis darauf dass der Parameter eine Referenz ist.

    blah

    quatsch

    Arcoth schrieb:

    Rohe Zeiger als Funktionsparameter sind in C++ IMO fast nur für Arrays sinnvoll.

    schon wieder bullshit

    schon wieder quatsch

    Arcoth schrieb:

    Für optionale Parameter gibt es Überladung.

    quatsch

    blah

    Arcoth schrieb:

    Und optionale Rückgabetypen sind std::optional (bald im TR) oder unique_ptr .

    nope, auch nich

    ganz sicher ja

    Arcoth schrieb:

    Nein, Referenzen haben ein sehr kleines Fehlerpotential.

    und das ganz sicher nich

    jup, ham se

    Arcoth schrieb:

    Nur hängende Referenzen stellen ein Problem dar.

    ok

    ja



  • mgaeckler schrieb:

    Arcoth schrieb:

    Bei welcher Gelegenheit liefert der Adressoperator eine NULL?

    *patsch*
    Du bist zu gutgläubig.

    Das beantwortet nicht meine Frage.

    Bei einem Pointer bei einer Funktion, wo man immer etwas gültiges haben will, muss man das dokumentieren, dass die Funktion keinen nullptr haben will.
    Bei einer Funktion mit Referenz, ist das klar, dass da immer ein gültiges Objekt hin soll.
    Die beste Dokumentation ist die, die man im Code sieht.
    Aus denselben Gründen verwende ich auch unsigned Typen.

    Jetzt aber mal ein Beispiel, das ich mit Referenzen nicht (oder langsamer) lösen kann:
    [...]

    Brauchste Pointer, nimm Pointer. Pointer sind nicht per sé schlecht.

    Arcoth schrieb:

    Dein Argument mit der & -Syntax, dass auf der call-site klarer wird was mit dem Argument geschieht, ist relativ schwach. Der Programmierer muss wissen was eine Funktion tut um einen Aufruf derer zu verstehen. Dementsprechend muss er auch die Funktionssignatur kennen. Und wenn er sie kennt, braucht er keinen Hinweis darauf dass der Parameter eine Referenz ist.

    Grundätzlich gebe ich Dir recht, aber wenn man sich neu in ein Projekt einarbeiten muß, ist das schon sehr hilfreich.

    Gut, das kann sein. Da man aber eh nicht immer weiß, was alle Parameter bedeuten sollte man sich sowieso die Funktionssignatur anzeigen lassen (geht bei IDEs ja schon automatisch.

    Merke: Zeiger sind gut beherschbar, wenn man die möglichen Probleme kennt und diese bei der Implementierung im Hinterkopf behält. Für komplexere Angelegenheiten sollte man natürlich irgendwelche Smart-Pointer benutzen.

    +1, aber komplexere Angelegenheiten sind besitzende Pointer. Kein rohe Pointer sollte eine Resource besitzen.


  • Mod

    Das beantwortet nicht meine Frage.

    Das mit der Gutgläubigkeit war darauf bezogen dass das Funktionsargument kein &Objekt -Ausdruck sein muss.

    Das geht schneller als man denkt.

    Referenzen sollten stets lokal sein. Nur als Funktionsparameter oder lokale Variablen auftreten.
    Hängende Referenzen können nur entstehen indem

    • Ein funktionslokales Objekt zurückgegeben wird, wobei der Rückgabetyp eine Referenz ist. Wird vom Compiler per Warnung angemeckert, die man natürlich stets aktiviert hat.
    • Allgemeinere Variante des oberen Stickpunkts: Das Objekt auf das eine Referenz verweist wird zerstört. Das dürfte niemals passieren da Referenzen keine Klassenmember sein sollten. Sie sollten auch nicht mit etwas initialisiert werden was zerstört wird bevor die Referenz out-of-scope geht.
    • die Referenz mit einem ungültigen lvalue initialisiert wird - bspw. Dereferenzierung eines Zeigers der auf kein Objekt zeigt. Hier ist das Problem auf einen hängenden Zeiger oder falsche Array-Indizen zurückzuführen. Ist der Zeiger uninitialisiert (und damit singulär) oder ein Null-Zeiger erzeugt die Dereferenzierung sowieso UB ohne die Referenz auch nur einzubeziehen.

    Schuld für die Situationen im letzten Stichpunkt haben sicherlich nicht Referenzen.

    Zeiger sind gut beherschbar, wenn man die möglichen Probleme kennt und diese bei der Implementierung im Hinterkopf behält.

    Ja, das ist richtig. 👍

    Nur wegen zwei Zeilen zwei Version einer 240 Zeilen langen Funktion?

    😮 Eine Funktion sollte i.d.R. nicht länger als 10-15 Zeilen lang werden!



  • Arcoth schrieb:

    Das beantwortet nicht meine Frage.

    Das mit der Gutgläubigkeit war darauf bezogen dass das Funktionsargument kein &Objekt -Ausdruck sein muss.

    Das hab ich mir schon fast gedacht.

    Arcoth schrieb:

    Das geht schneller als man denkt.

    Referenzen sollten stets lokal sein. Nur als Funktionsparameter oder lokale Variablen auftreten.
    Hängende Referenzen können nur entstehen indem

    • Ein funktionslokales Objekt zurückgegeben wird, wobei der Rückgabetyp eine Referenz ist. Wird vom Compiler per Warnung angemeckert, die man natürlich stets aktiviert hat.
    • Allgemeinere Variante des oberen Stickpunkts: Das Objekt auf das eine Referenz verweist wird zerstört. Das dürfte niemals passieren da Referenzen keine Klassenmember sein sollten. Sie sollten auch nicht mit etwas initialisiert werden was zerstört wird bevor die Referenz out-of-scope geht.
    • die Referenz mit einem ungültigen lvalue initialisiert wird - bspw. Dereferenzierung eines Zeigers der auf kein Objekt zeigt. Hier ist das Problem auf einen hängenden Zeiger oder falsche Array-Indizen zurückzuführen. Ist der Zeiger uninitialisiert (und damit singulär) oder ein Null-Zeiger erzeugt die Dereferenzierung sowieso U ohne die Referenz auch nur einzubeziehen.

    Schuld für die Situationen im letzten Stichpunkt hat sicherlich nicht die Referenz.

    Es gibt bestimmt noch mehr Möglichkeiten. sowas z.B.:

    std::vector< anyType > myVector;
    
    ....
    
    anyType &myType = myVector[i];
    
    .... // any thing that "could" change the vector
    
    myType.member = 1;
    

    Das Gemeine daran ist, das kann mal funktionieren aber muß nicht.

    Arcoth schrieb:

    Nur wegen zwei Zeilen zwei Version einer 240 Zeilen langen Funktion?

    😮 Eine Funktion sollte i.d.R. nicht länger als 10-15 Zeilen lang werden.

    Ja, wünschenswert ist das sicherlich. Aber leider nicht immer ohne Klimmzüge realisierbar.

    mfg Martin



  • icarus2 schrieb:

    //- call by reference
    void functions2(int* b)
    

    ist nicht Call by Reference. Der Pointer (den man als Argument übergibt) wird kopiert, d.h. es handelt sich um Call by Value.

    C und Java unterstützen kein Call by Reference. Man kann zwar einen Pointer (in C) oder eine Referenz (in Java) übergeben aber diese werden stets kopiert.

    Also so wie ich das sehe handelt es sich nicht um call-by-value weil du ja nicht den Wert (value) sondern die Adresse übergibst. Zwar wird ja trotzdem ein Pointer angelegt der im Speicher ist, allerdings hat dieser die Adresse der übergebenen Variablen. Somit würde ich nicht von "call-by-value" sprechen.

    Kritik ist erwünscht.


  • Mod

    anyType &myType = myVector[i];
     
    .... // any thing that "could" change the vector
     
    myType.member = 1;
    

    ➡

    Arcoth schrieb:

    Allgemeinere Variante des oberen Stickpunkts: Das Objekt auf das eine Referenz verweist wird zerstört. [...] Sie sollten auch nicht mit etwas initialisiert werden was zerstört wird bevor die Referenz out-of-scope geht.

    Ja, wünschenswert ist das sicherlich. Aber leider nicht immer ohne Klimmzüge realisierbar.

    Kompensieren die Vorteile nicht die Klimmzüge?

    Also so wie ich das sehe handelt es sich nicht um call-by-value weil du ja nicht den Wert (value) sondern die Adresse übergibst.

    Eine Adresse ist auch ein Wert. Ein Zeiger hat einen Wert, nämlich eine Adresse.



  • Arcoth schrieb:

    anyType &myType = myVector[i];
     
    .... // any thing that "could" change the vector
     
    myType.member = 1;
    

    ➡

    Arcoth schrieb:

    Allgemeinere Variante des oberen Stickpunkts: Das Objekt auf das eine Referenz verweist wird zerstört. [...] Sie sollten auch nicht mit etwas initialisiert werden was zerstört wird bevor die Referenz out-of-scope geht.

    Aber das sieht man wie in meinem Beispiel nicht immer. Aber Du hast recht, ist im Grunde genommen in Deinem Punkt enthalten.

    Arcoth schrieb:

    Ja, wünschenswert ist das sicherlich. Aber leider nicht immer ohne Klimmzüge realisierbar.

    Kompensieren die Vorteile nicht die Klimmzüge?

    Nicht immer. Gerade wenn auch die Performanz ein Problem ist.

    mfg Martin


  • Mod

    Gerade wenn auch die Performanz ein Problem ist.

    Man kann das Inlinen von Funktionen forcieren (bspw. durch das Attribut always_inline beim GCC).



  • [quote="Arcoth"]

    Shor-ty schrieb:

    Also so wie ich das sehe handelt es sich nicht um call-by-value weil du ja nicht den Wert (value) sondern die Adresse übergibst.

    Eine Adresse ist auch ein Wert. Ein Zeiger hat einen Wert, nämlich eine Adresse.

    Ja gut okay, wo du recht hast hast du recht. Ich dachte - durch meine »noch« sehr große Unwissenheit - das mit VALUE nur der WERT der Variablen gemeint ist und nicht die Adresse. Aber sicherlich hast du recht zu sagen das eine Adresse auch ein Wert ist. Danke für eure ausführlichen Antworten.

    Viele Grüße
    Tobi



  • mgaeckler schrieb:

    Aus diesem Grund verwende ich bei Funktionsdefinitionen, wenn ich call by reference brauche eher selten Referenzen sondern lieber Zeiger. In diesem Fall sieht man nämlich die Aufrufart auch beim Aufrufer. Wenn ich aber call bei referece nur aus Performanzgründen nutze und ich den Wert eh nicht verändern will, dann nehme ich auch schon mal 'ne const reference:

    Geht mir auch so. Ich meine, daß ich dadurch viel mehr Zeit beim Code-Lesen spare, als ein paar Nullzugriffe kosten könnten. Nullzugriffe findet man dochj sofort. Aus Versehen eine schreibende Funktion aufgerufen fällt einem bei der Referenzvariante gar nicht auf.


Anmelden zum Antworten