Verwendung unterschiedlicher Parameteruebergabearten



  • Das ist Pass-by-Pointer:

    void f(int* var);
    

    und nicht by Reference!

    Auch Pass-by-Reference - Arbeitet mit dem Originalwert, aber erlaubt keine Modifizierung dessen.

    Falsch! Das der Wert nicht änderbar ist, hat nicht mit der Reference zu tun. Es hat was mit dem Schlüsselwort 'const' zu tun! Schau mal hier:

    void f(const int* var);
    

    Jetzt ist var auch nicht änderbar! Und das hier:

    void f(const int var);
    

    Ist var auch nicht änderbar.
    So, zu den Pointern und Referenzen: Das sind zwei verschieden paar Schuhe!

    Ein Pointer ist eine Variable, die eine Speicheradresse enthält.

    Eine Referenz ist nur ein anderer Name für ein bereits vorhandenes Objekt.

    D.h. die Parameterübergaben (alle drei) haben total unterschiedliches Verhalten und Bedeutung! Dabei ist es wichtig, ob bei allen dreien ein 'const' davor steht. Nur das const macht den Parameter nicht änderbar. Hat nichts mit by-Value, by-Pointer oder by-Ref zu tun!

    2. Gibt es unterschiedliche Bezeichnungen fuer die beiden Pass-by-Reference Moeglichkeiten, wenn ja, wie nennt man sie?

    Ja, by-Pointer und by-Reference. Logisch, oder?

    3. Warum nutzt man nicht einfach Pass-by-Reference mit & aber ohne const (letzte), wenn man den Originalwert veraendern will; sondern eher die Moeglichkeit ueber den Zeiger (mittlere)? Oder Welchen Nachteil haette ein f(int &var) gegenueber f(int *pVar) oder auch im allg.?

    Wer sagt denn, das Pass-by-Ref OHNE const nicht genutz wird? Ich benutze das immer, wenn ich das haben will. Einen Nachteil gibt es nicht.

    4. Sollte man generell eher einen Pass-by-Value vermeiden zugunsten der letzten Pass-by-Reference Uebergabe (wegen Performance, Uebersichtlichkeit oder ???)?

    Kommt auf die Situation und auf den Zweck an. Wenn ich eine Kopie haben will, mache ich auch auch Pass-by-Value. Sagen wir mal, ich will ein Flugzeug haben, das ich ändern will. Aber ich will nicht das orig. Flugzeug haben. Dann mache ich natürlich das:

    void foo(Flugzeug f);
    

    In dem Moment interessiert mich Performance nicht, weil hier der Context meines Algorithmus im Vordergrund steht! Wenn ich aber vom Funktionsbenutzer DAS originale Objekt haben und ändern will, dann mache ich das:

    void foo(Flugzeug& f);
    

    Ich entscheide mich in beiden Fällen nicht aus Perfomancegründen für etwas, sondern aus Programmverhaltens/-ablaufgründen. Wenn ich aber nur ein Objekt haben will, das ich nicht ändern will, mache ich das:

    void foo(const Flugzeug& f);
    

    oder

    void foo(const Flugzeug f);
    

    Beide erfüllen den gleichen Zweck. Ohne das ich über Perfomance nachdenke. Aus Performance-Gründen, wähle ich erstere Variante. Ein guter Compiler kann aber die zweite Variante (const by-Value) sogar als Variante 1 kompilieren (ohne das ich das als Coder merke!).
    Wann benutze ich by-Pointer Paramter?
    Wenn ich auch einen NULL-Wert erlauben will. Z.B. es soll mir jemand entweder ein Flugzeug oder auch kein Flugzeug übergeben können:

    void foo(Flugzeug* f);
    ...
    {
       foo(NULL);  // habe kein Flugzeug-Objekt das ich übergeben könnte.
    }
    


  • Artchi schrieb:

    (...)

    void foo(const Flugzeug& f);
    

    oder

    void foo(const Flugzeug f);
    

    Beide erfüllen den gleichen Zweck. Ohne das ich über Perfomance nachdenke. Aus Performance-Gründen, wähle ich erstere Variante. Ein guter Compiler kann aber die zweite Variante (const by-Value) sogar als Variante 1 kompilieren (ohne das ich das als Coder merke!).

    Äh. Das ist irreführend. Ein guter Compiler kann auch ohne das const in der Variante ohne Referenz alles wegoptimieren. Und wenn er es ohne const nicht kann kann er es mit auch nicht. Von daher...
    Das const in

    void foo(const Flugzeug f);
    

    ist nämlich vollkommen für nix, du kannst diese Funktion ohne weiteres als

    void foo(Flugzeug f) { f.mutator(); }
    

    implementieren, sagt kein Compiler und kein Standard was dagegen.



  • Hallo nochmal, Danke fuer die Antworten, bisher kannte ich nur entweder Pass-by-Value oder Pass-by-Reference, daher war es fuer mich schwierig, wo ich denn nun "Pass-by-Pointer" einzuordnen habe - klar ein pointer wird quasi "by-Value" uebergeben, aber andererseits habe ich aehnliche Moeglichkeiten mit dem Inhalt auf den der Pointer zeigt, wie mit by-Reference. Ich werde nun in Zukunft in dem Zusammenhang als by-Pointer sprechen, danke.

    Das mit dem "const" (@Artchi) war mir klar. Ich habe es nur dazu geliefert, da ich gelesen hatte, dass es oft sinnvoll ist um eben abzusichern, dass der Originalwert nicht veraendert wird, wenn man by-Reference uebergibt. Danke trotzdem fuer die ausfuehrliche Erklaerung nochmal.

    Der Haupt-Unterschied von by-Reference (mit const) zu by-Value erschien mir hier als, dass eben keine separate Kopie der Variable erstellt wird. So, bei dem Schlagwort "Performance" dachte ich deshalb hier auch an die Argumentation, die Schue oben ausfuehrte, mit dem Unterschied von primitiven Typen und grossen Objekten. Ich glaube das auch mal wo gelesen zu haben, war mir aber eben nicht mehr ganz sicher - jetz bin ich's wieder.

    Die fundamentale Erkenntnis des Abends (Morgens) ist fuer mich allerdings folgende:

    Artchi schrieb:

    Wenn ich auch einen NULL-Wert erlauben will. Z.B. es soll mir jemand entweder ein Flugzeug oder auch kein Flugzeug übergeben können:

    void foo(Flugzeug* f);
    ...
    {
       foo(NULL);  // habe kein Flugzeug-Objekt das ich übergeben könnte.
    }
    

    Danke, das hat mir wieder etwas die Augen geoeffnet!!! 😮



  • Naja, ich (als Anfaenger) halte mich bisher an folgende reglung:

    void foo(type v)
    

    wenn ich in der Funtkuion eh eine Kopie von v brauchen werde (z.B weil die Kopie modifiziert wird, das Original aber bestehen bleiben soll) oder bei kleinen eingebauten Typen wie int wo das kopieren eh recht egal ist.

    void foo(type& v)
    

    wenn es eine Funktion ist, die den Wert von v aendern soll und von der es absolut ersichtlich ist, dass sie den Wert von v aendern wird! (im Programm schreibt man dann ja einfach foo(v); das koennte ja pass by value oder by reference sein, darum NUR wenn es leicht ersichtlich ist.

    void foo(const type& v)
    

    wenn ich den Wert in der Funtkion weder aendere noch kopieren muss. SPart Zeit und trotzdem ist der Aufruf sehr leicht.

    void foo(type* v)
    

    wenn ich den Wert in der Funtkion aendern werde und das nicht unbedingt direkt (aus dem Sinn der Funtkion) ersichtlich ist. In dem Fall muss man die Funtkion ja im Programm als foo(&v); aufrufen,. dadurch wird dann klar, dass ich eben eine Referenz uebergebe und v somit sehr wahrscheinlich modifiziert wird.

    Ich weiss nicht wie andere das halten, aber ich finde es so recht leicht und uebersichtlich.

    PS: bei kleineren Programmen achte ich manchmal auch nicht so drauf und uebergebe immer per reference, da ich mir da ja eh leicht merken kann, was welche Funtkion macht)



  • Ich weis, ist kleinlich. Aber es sind gerade die Details beim proggen 😉

    Fabeltier schrieb:

    Hallo nochmal, Danke fuer die Antworten, bisher kannte ich nur entweder Pass-by-Value oder Pass-by-Reference, daher war es fuer mich schwierig, wo ich denn nun "Pass-by-Pointer" einzuordnen habe - klar ein pointer wird quasi "by-Value" uebergeben,

    Ein Pointer wird nicht "quasi" by-Value übergeben, sondern er wird by-Value übergeben. Punkt.

    Fabeltier schrieb:

    aber andererseits habe ich aehnliche Moeglichkeiten mit dem Inhalt auf den der Pointer zeigt, wie mit by-Reference.

    Du vermischst hier Pointer und Pointee. Das, worauf der Pointer zeigt wird zwar gerne als dessen "INhalt" betrachtet, stimmt aber nicht. Technisch betrachtet ist der Pointer eine Variable deren Inhalt eine Speicheradresse ist.

    Das, worauf er zeigt, aka die Adresse die er hat, ist der Pointee.



  • Noch eine Ergänzung: Die Übergabe per-Pointer triffst du häufig in C-Code an, weil es dort noch keine Referenzen gab. (und auch unter C++ verwendet man gelegentlich Funktionen aus der C-Bibliothek)

    @Shinja: Wenn aus der Funktion nicht ersichtlich ist, ob sie ihre Parameter ändern will, hast du irgendwas bei der Benennung falsch gemacht 😉 Und normalerweise schaut man sich zumindest den Prototyp der Funktion an, bevor man sie verwenden will - und da sieht man den Unterschied zwischen "foo(typ x);" und "foo(typ**&** x);".



  • hustbaer schrieb:

    Das const in

    void foo(const Flugzeug f);
    

    ist nämlich vollkommen für nix, du kannst diese Funktion ohne weiteres als

    void foo(Flugzeug f) { f.mutator(); }
    

    implementieren, sagt kein Compiler und kein Standard was dagegen.

    Ehm, aber dein zweites foo sagt dem Benutzer von foo nicht, das f nicht modifiziert wird! Und das ist nicht gut! Ich will wissen, ob f geändert wird, auch wenn es eine Kopie ist! Warum? Darum:

    class Flugzeug 
    {
       private:
          Flughafen *fh;
       public:
          Flughafen* getFH();
    };
    
    void foo(Flugzeug f)
    {
        f.getFH()->setName("Frankfurt"); // keine Änderung direkt an f, aber ich mache eine Änderung!
    }
    

    So, der Flughafen bleibt bei einer Kopie der gleiche. 😮 Und foo könnte daran was rummachen. Also, wie von mir gesagt, das const ist IMMER wichtig. Das was du machst, ist wischi-waschi-Arbeit, da du const als Parameter-Modifier für unwichtig erklärst. Aber const ist immer angebracht, wenn sobald ich ein Objekt nicht ändern will.



  • Hallo Artchi,

    bei folgendem Code ändert die Funktion foo trotzdem intern Werte, auch wenn der Parameter als const deklariert ist (weil ich getFH als const deklariert habe, da die Methode selbst keine Änderungen am Objekt vornimmt):

    class Flughafen
    {
    public:
    	void setName(const char *c);
    };
    
    class Flugzeug 
    { 
    private: 
    	Flughafen *fh; 
    public: 
    	Flughafen* getFH() const; 
    }; 
    
    void foo(const Flugzeug f) 
    { 
    	f.getFH()->setName("Frankfurt"); // keine Änderung direkt an f, aber ich mache eine Änderung! 
    }
    

    Ich persönlich halte nichts von "const Type" bei Funktionsparametern, da ja sowieso eine Kopie übergeben wird und es dem Aufrufer ziemlich egal sein kann, ob der Parameter verändert wird oder nicht (z.B. bei einer Längenangabe und dann wird dieser Wert intern als Zähler benutzt, anstatt ein lokale temp. Variable anzulegen).
    Bei Call-By-Ref und Call-By-Pointer ist der Parameter "const" natürlich entscheidend und sehr wichtig!



  • schue schrieb:

    Das, worauf er zeigt, aka die Adresse die er hat, ist der Pointee.

    Ok, by-Value Uebergabe des Zeigers, das ist mir schon klar, aber ausgehend vom "Inhalt des Pointers" (s.u.)? Irgendwie bleibt es ein Zwischending ausgehend vom Standpunkt des Betrachters - rein systematisch: Pass-by-Value eines Zeigers, mehr praktisch gesehn und von einem Pointee betrachtet, irgendwass anderes, also am besten gleich: Pass-by-Pointer (das gefaellt mir am besten).

    Also ich geb zu, da etwas eine eigene Namensgebung entwickelt zu haben, was ich mir gerade wieder abgewoehnen will. Bei einem Zeiger hab ich "Adresse des Zeigers", "Adresse auf die der Zeiger zeigt" und den "Inhalt der Adresse auf die der Zeiger zeigt" - welches ist der Pointee? Der Inhalt oder die Adresse auf d.d.Zg.zgt.? 😉



  • Fabeltier schrieb:

    schue schrieb:

    Das, worauf er zeigt, aka die Adresse die er hat, ist der Pointee.

    Ok, by-Value Uebergabe des Zeigers, das ist mir schon klar, aber ausgehend vom "Inhalt des Pointers" (s.u.)? Irgendwie bleibt es ein Zwischending ausgehend vom Standpunkt des Betrachters - rein systematisch: Pass-by-Value eines Zeigers, mehr praktisch gesehn und von einem Pointee betrachtet, irgendwass anderes, also am besten gleich: Pass-by-Pointer (das gefaellt mir am besten).

    Das dürfte auch die gängigste Bezeichnung sein (außer bei C-Entwicklern, die keine C++ Referenzen kennen und deshalb func(T*) als pass-by-Reference bezeichnen).

    Also ich geb zu, da etwas eine eigene Namensgebung entwickelt zu haben, was ich mir gerade wieder abgewoehnen will. Bei einem Zeiger hab ich "Adresse des Zeigers", "Adresse auf die der Zeiger zeigt" und den "Inhalt der Adresse auf die der Zeiger zeigt" - welches ist der Pointee? Der Inhalt oder die Adresse auf d.d.Zg.zgt.? 😉

    Pointer = Zeiger, Pointee = verzeigertes Objekt (also "Inhalt der Adresse, auf die der Zeiger zeigt").

    Und was du als "Adresse des Zeigers" bezeichnest, ergibt letztendlich einen Doppelzeiger.



  • CStoll schrieb:

    Und was du als "Adresse des Zeigers" bezeichnest, ergibt letztendlich einen Doppelzeiger.

    Genau das meinte ich - bisher nannte ich es auch "Pointer-Pointer"..



  • Artchi schrieb:

    hustbaer schrieb:

    Das const in

    void foo(const Flugzeug f);
    

    ist nämlich vollkommen für nix, du kannst diese Funktion ohne weiteres als

    void foo(Flugzeug f) { f.mutator(); }
    

    implementieren, sagt kein Compiler und kein Standard was dagegen.

    Ehm, aber dein zweites foo sagt dem Benutzer von foo nicht, das f nicht modifiziert wird! Und das ist nicht gut! Ich will wissen, ob f geändert wird, auch wenn es eine Kopie ist! Warum? Darum:

    class Flugzeug 
    {
       private:
          Flughafen *fh;
       public:
          Flughafen* getFH();
    };
    
    void foo(Flugzeug f)
    {
        f.getFH()->setName("Frankfurt"); // keine Änderung direkt an f, aber ich mache eine Änderung!
    }
    

    So, der Flughafen bleibt bei einer Kopie der gleiche. 😮 Und foo könnte daran was rummachen. Also, wie von mir gesagt, das const ist IMMER wichtig. Das was du machst, ist wischi-waschi-Arbeit, da du const als Parameter-Modifier für unwichtig erklärst. Aber const ist immer angebracht, wenn sobald ich ein Objekt nicht ändern will.

    Du verstehst da was nicht. Wenn ich heute noch Zeit habe poste in ein kleines Demoprogramm. Der Punkt ist der, das "const" bei "by-value" Parametern ist vollkommen für NIX, das sagt dem Benutzer nichts. Dort wo die Funktion implementiert wird braucht kein const mehr stehen, und selbst wenn, dann mache ich mir eben nochmal eine Kopie die dann nicht const ist, und greife auf die dann non-const zu.



  • Hast Recht. Seh ich jetzt auch.



  • Nur um das für den interessierten Laien nochmal zu ergänzen - selbst bei einer Übergabe per const-Reference kann ich mir eine lokale non-const-Kopie machen. Wenn ich darüber dann aber ein Objekt manipulieren kann, auf das auch ein Member der const-Reference zeigt, ist das entweder Absicht oder die Kopiersemantik ist falsch 😉



  • Wenn man das ganze mal aus klassischer Ansicht betrachtet, da gibt es im Wesentlichen zwei Arten der Parameterübergabe: Pass by value (Änderungen am parameter sind außen nicht sichtbar), pass by reference (Änderungen sind außen sichtbar). Der Vorteil dieser beiden Definitionen ist, daß man sie auf nahezu jede Programmiersprache anwenden kann.

    In dieser Nomenklatur kann man schon sagen, dass Übergabe per Pointer ein pass by reference ist. Wer's besonders genau sagen möchte könnte auch sagen, daß man damit pass by reference implementiert. Daß da in Wirklichkeit ein Pointer by value übergeben wird spielt für die Einordnung keine Rolle. Das ist bei einer echten referenz schließlich auch nicht anders.

    Allein das Verhältnis er Suchergebnisse von "pass by reference" und "pass by pointer" bei googlefight zeigt, dass "pass by pointer" kein wirklich gängiger Begriff ist.



  • Nochmal zu dem const - ich experimentiere gerade auch damit rum und werde demnaechst sicherlich noch mind. einen laengeren "Grundsatzdialog" diesbezueglich vom Zaun brechen. Hier gings mir v.a. um das genannte Thema (sh. Ueberschrift), was fuer mich nun klar gewoerden ist.

    @ Jester:
    Vom Verstaendnis her wuerde ich es auch genau so sehen, aber wie man hier schon lesen kann, ist "by-Pointer" schon auch ein Punkt den man durchdiskutieren kann. Das genannte Verhalten ist jedenfalls so implementierbar - sodass exakte Definition eines Begriffes und "das was man damit meint" zwar immer zwei paar Stiefel sind, die sich letztendlich wohl aber auch nicht selbst zum Thema machen sollten, solange man versteht wovon man spricht.


Anmelden zum Antworten