Mehrfachreferenzen



  • Athar schrieb:

    Wie kann ich wissen ob das Objekt hinter der Referenz noch lebt?

    Indem du korrekten Code schreibst, dann ist das nämlich immer der Fall.

    In der Theorie ja. Aber warum dann groß Referenzen nehmen? Pointer kann ich auf NULL testen, Referenzen nicht.
    Beispiel:

    class foo
    {
      OtherClass* m_a;
    
      foo() : m_a(new OtherClass)
      {}
    
      OtherClass& getA() { return *m_a; }
    }
    

    Wenn das Objekt m_a gelöscht wurde, crasht mir das hier quasi einfach weg? Gibts keine NullReference wie in Java?
    lg



  • Gute Referenzen schrieb:

    In der Theorie ja. Aber warum dann groß Referenzen nehmen? Pointer kann ich auf NULL testen, Referenzen nicht.

    Es werden ja auch viel zu viele Referenzen statt Pointern genommen. Referenzen sind nötig, um Operatorüberladung und Kopierkostruktoren hinzukriegen.

    Gute Referenzen schrieb:

    Wenn das Objekt m_a gelöscht wurde, crasht mir das hier quasi einfach weg? Gibts keine NullReference wie in Java?

    Nee, das ist verboten. Es klappt zwar, aber offiziell klappt es nicht.
    Wenn man NULL auch drin braucht, MUSS man Zeiger nehmen.



  • volkard schrieb:

    Gute Referenzen schrieb:

    In der Theorie ja. Aber warum dann groß Referenzen nehmen? Pointer kann ich auf NULL testen, Referenzen nicht.

    Es werden ja auch viel zu viele Referenzen statt Pointern genommen. Referenzen sind nötig, um Operatorüberladung und Kopierkostruktoren hinzukriegen.

    Was ist an der Referenz hier schlecht/zu viel?

    void foo(Class const& object)
    

    Damit muss doch nur der Aufrufer sicher stellen, dass er mir ein gültiges Objekt gibt und ich muss nicht in der Funktion (nochmal) auf NULL prüfen.



  • Referenz schrieb:

    krümelkacker schrieb:

    Dementsprechend ist

    int & r2 = r;
    cout << r2;
    

    vergleichbar mit

    int * p2 = &r; // Beachte: &r == &i
    cout << *p2;
    

    Aber wäre p2 dann nicht ein Zeiger auf eine Referenz?

    Nein. Wie gesagt, eine Referenz ist wie ein sich automatisch dereferenzierender Zeiger. Daher entspricht der Ausdruck &referenz eher &*zeiger , welches die Adresse des referenzierten Objekts liefert.

    Aber warum dann groß Referenzen nehmen? Pointer kann ich auf NULL testen, Referenzen nicht.

    Referenzen sind praktisch für "pass by reference". Man kann eine Referenz-auf-const verwenden, wenn die Funktion das Objekt nicht ändern muss. Damit spart man das Kopieren des Objekts; denn mit pass-by-value werden die Objekte kopiert:

    void dings(string s) { cout << s; }
    void bums(string const& cref) { cout << cref; }
    
    int main()
    {
      string hw = "hello world!";
      dings(hw); // #1
      bums(hw);  // #2
    }
    

    Bei #1 muss für die Funktion eine lokale Kopie des string-Objekts erzeugt werden. Wenn dings ihr s verändert, hat das keinen Einfluss auf das hw Objekt. Das Kopieren kann aber u.U. Zeit/Speicher kosten. dings muss s nicht verändern. Bei #2 wird nur eine Referenz auf das Objekt übergeben. Da das referenzierte Objekt auch const-qualifiziert ist, kann die Funktion bums nicht versehentlich das hw-Objekt verändern. Wir haben uns hier eine unnötige Kopie gespart.

    Nicht-Const-Referenzen können auch praktisch sein:

    void swap(int& a, int& b)
    {
      int c = a; a = b; b = c;
    }
    
    int foo()
    {
      int a = 23;
      int b = 42;
      swap(a,b);
      cout << a << ' ' << b;  // gibt 42 23 aus
    }
    

    Klar, mit Zeigern geht das auch:

    void swap(int* a, int* b)
    {
      if (!a || !b) throw std::invalid_argument("ich will keine Nullzeiger!")
      int c = *a; *a = *b; *b = c;
    }
    
    int foo()
    {
      int a = 23;
      int b = 42;
      swap(&a,&b);
      cout << a << ' ' << b;  // gibt 42 23 aus
    }
    

    In anderen Fällen sind sie aber notwendig:

    class meine_array_abstraktion
    {
      int* start;
      int* ende;
    public:
      explicit meine_array_abstraktion(std::size_t anzahl_elemente = 0);
      meine_array_abstraktion(meine_array_abstraktion const&); // #1
      meine_array_abstraktion& operator=(meine_array_abstraktion const&); // #2
      ~meine_array_abstraktion();
      int      & operator[] (std::size_t index)       {return start[index]} // #3
      int const& operator[] (std::size_t index) const {return start[index]}
      std::size_t size() const {return ende-start;}
    };
    

    #1: Der Kopierkonstruktor muss eine Referenz entgegennehmen.
    #2: Der Zuweisungsoperator sollte eine Referenz auf *this zurückgeben
    #3: Diese Funktion, welche eine Referenz zurückgibt, erlaubt dem Nutzer das Verändern eines Elements mit der natürlichen Syntax:

    meine_array_abstraktion maa = ...;
      maa[0] = 23;
      maa[1] = 42;
      maa[2] = 1729;
    

    Referenzen, nachdem sie initialisiert wurden, sind unveränderlich -- also, man kann sie nicht dazu bringen, nachträglich etwas anderes zu referenzieren. Durch diese drei Zuweisungen werden die referenzierten Objekte verändert. Das ergibt sich auch wieder aus dem "initialisierte Referenzen verhalten sich wie sich selbst dereferenzierende Zeiger"-Gedanken.



  • DeepPurple schrieb:

    Damit muss doch nur der Aufrufer sicher stellen, dass er mir ein gültiges Objekt gibt und ich muss nicht in der Funktion (nochmal) auf NULL prüfen.

    Muß ich auch so nicht. Testet strcpy gegen NULL? Es ist doch offensichtlicher Schwachsinn, NULL zu übergeben. Auch beim folgenden swap-Beispiel. Dagegen muß keiner Testen. Und vor allem nicht mit throw den Fehler anzeigen.
    Wer das macht, der frißt auch kleine Kinder und sagt if(&ref!=NULL). krümelkacker hat das jetzt nur mal zur Demo gemacht.



  • volkard schrieb:

    DeepPurple schrieb:

    Damit muss doch nur der Aufrufer sicher stellen, dass er mir ein gültiges Objekt gibt und ich muss nicht in der Funktion (nochmal) auf NULL prüfen.

    Muß ich auch so nicht. Testet strcpy gegen NULL? Es ist doch offensichtlicher Schwachsinn, NULL zu übergeben. Auch beim folgenden swap-Beispiel. Dagegen muß keiner Testen. Und vor allem nicht mit throw den Fehler anzeigen.
    Wer das macht, der frißt auch kleine Kinder und sagt if(&ref!=NULL). krümelkacker hat das jetzt nur mal zur Demo gemacht.

    Und wie beantwortet das jetzt meine Frage?

    Was ist an der Referenz hier schlecht/zu viel?

    void foo(Class const& object)
    

    Oder, was ist hier besser?

    void foo(Class const* object)
    


  • Das kommt auf deinen Stil an. Im Wesentlichen gibt es zwei Fraktionen:

    • Manche Leute verwenden Const-Referenzen nur zur Beschleunigung, die Semantik wäre gleich wie bei einer Kopie. Sie würden aber intern keine Zeiger darauf speichern. Wenn das Objekt verändert wird, übergeben sie Zeiger. Referenzen auf nicht-konstante Objekte haben gar keine Verwendung.
    • Andere benutzen in Parameterlisten Zeiger, wenn NULL ein gültiger Wert ist, und sonst Referenzen. Ich handhabe das z.B. so.


  • Nexus schrieb:

    Das kommt auf deinen Stil an. Im Wesentlichen gibt es zwei Fraktionen:

    • Manche Leute verwenden Const-Referenzen nur zur Beschleunigung, die Semantik wäre gleich wie bei einer Kopie. Sie würden aber intern keine Zeiger darauf speichern. Wenn das Objekt verändert wird, übergeben sie Zeiger. Referenzen auf nicht-konstante Objekte haben gar keine Verwendung.
    • Andere benutzen in Parameterlisten Zeiger, wenn NULL ein gültiger Wert ist, und sonst Referenzen. Ich handhabe das z.B. so.

    Das schließt sich doch gar nicht aus. Oder verstehe ich da etwas falsch?



  • logik schrieb:

    Das schließt sich doch gar nicht aus.

    Doch. Leider kann man nicht die Vorteile beider Varianten benutzen. Entweder drückt ein Zeiger in der Schnittstelle aus, dass das Objekt verändert wird oder dessen Adresse gespeichert wird (und verunmöglicht gerechtfertigterweise temporäre Objekte), oder er impliziert, dass NULL übergeben werden darf.

    // ---- Fraktion 1 ---------------------------------------------
    void Fn(const T& ref)
    {
        // Speichere nirgends die Adresse von ref
    }
    
    void Fn(T* ptr)
    {
        // Verändere *ptr oder speichere dessen Adresse
        // Es ist für den Aufrufer unklar, ob NULL erlaubt ist
    }
    
    void Fn(const T* ptr)
    {
        // Speichere ptr
        // Es ist für den Aufrufer unklar, ob NULL erlaubt ist
    }
    
    // ---- Fraktion 2 ---------------------------------------------
    void Fn(T& ref)
    {
        // Möglicherweise Speicherung von &ref
        // ref kann verändert werden
    }
    
    void Fn(const T& ref)
    {
        // Möglicherweise Speicherung von &ref
    }
    
    void Fn(T* ptr)
    {
        // NULL ist sicher ein erlaubter Wert
        // Falls gültiger Zeiger, verändere *ptr oder speichere ptr
    }
    
    void Fn(const T* ptr)
    {
        // NULL ist sicher ein erlaubter Wert
        // Speichere ptr
    }
    


  • Referenzen auf const zum Vermeiden einer Kopie sind immer ok. (Abgesehen von bestimmten Compileroptimierungen.)
    Bei non-const ist der Unterschied da. Zum Glück kommen die selten vor.


Anmelden zum Antworten