Pointer vs. Reference



  • Tachyon schrieb:

    Das sagt eigentlich deutlich, dass bei Referenzen technisch nicht zwangsläufig mit Zeigern realisiert werden müssen...

    Ja, das ist wie mit OOP, wo Nachrichten ausgetauscht werden, statt Funktionen zu rufen. Man könnte statt die Funktion zu rufen, auch eine Nachricht per Messagebox ausgeben und den Benutzer bitten, die Nachricht an ein anderes Objekt weiter zu leiten. Sowas ist grundsätzlich nicht von der OOP-Idee ausgeschlossen. In der Praxis haben sich Funktionspointer bewährt.

    Ähnlich ist das hier. Natürlich muss man Referenzen nicht mit Zeigern implementieren. Man könnte zum Beispiel das Objekt kopieren, bearbeiten lassen und bei Rückkehr der Funktion auf das Originalobjekt zurückkopieren. Semantisch wäre das eine dem Standard entsprechende Vorgehensweise, die selbstverständlich auch zum gleichen Ergebnis führt. Es wird lediglich etwas kompliziert, wenn man eine Referenz als Teil eines anderen Objektes hat, aber da lässt sich bestimmt auch was über die MMU tricksen.

    Finde ich gut, dass Du drauf hingewiesen hast. 👍
    Aus irgendeinem Grund sind sich die Compilerentwickler aber meines Wissens relativ einig gewesen, Referenzen mit Pointern zu realisieren.



  • Xin schrieb:

    [...]

    Du hast die Message nicht verstanden. Die Message war nicht: Es wird nicht mit Zeigern relisiert, sondern irgendwie magisch hingefrickelt.

    Die Message war: Es darf einen nicht intressieren wie es realisiert wird, weil Annahmen darüber gefährlich sein können und auch sind. Und ultimativ auch: Es braucht einen nicht zu interessieren.

    Xin schrieb:

    Aus irgendeinem Grund sind sich die Compilerentwickler aber meines Wissens relativ einig gewesen, Referenzen mit Pointern zu realisieren.

    Meines Wissens machen die Compilerentwickler es so, wie es kontextabhängig am meisten Sinn hat.



  • Tachyon schrieb:

    Xin schrieb:

    [...]

    Du hast die Message nicht verstanden. Die Message war nicht: Es wird nicht mit Zeigern relisiert, sondern irgendwie magisch hingefrickelt.

    Du hast meine Message nicht verstanden.

    Wir arbeiten mit Computern. Es gibt Best-Practices. Manchmal nennt man sie auch Hochsprachen, Design-Pattern oder Standards. Computer haben eine Funktionsweise und Pointer sind dabei nicht etwas magisch hingefrickeltes, sondern eine grundlegende Lösung ohne Alternative. Nicht nur eine Best-Practice, sondern eine Grundlage der Funktion.

    Der Standard schreibt nicht vor Pointer zu verwenden. Wir können hier gerne einen Einsteiger mit einer Philosophie über den C++-Standard beglücken, aber es wird ihm wesentlich mehr nutzen, wenn er weiß, dass Referenzen technisch über Pointer realisiert werden. Das erklärt ihm vergleichsweise einfach Referenzen, wie sich Referenzen verhalten und warum bei sizeof( int & ) ein bestimmtes Ergebnis zu erwarten ist.

    Wenn also jeder hammerkompatible Nägel herstellt, Hämmer dafür bekannt sind, um Nägel in die Wand zu referenzieren (<-man beachte das Wort), er die Funktionsweise eines Hammers begreifen kann, damit auch den Nagel in die Wand bekommt, dann ist der Thread für ein praktisches Problem erledigt.

    Auch wenn das Gesetz sagt, dass Du zum Nägelklopfen auch andere Werkzeuge verwenden darfst, dann kannst das mit einem Theoretiker gerne ausdiskutieren. Ich bin diesbezüglich so einer, denn ich entwickle einen Compiler. Und auch ich will darüber nicht philosophieren, da wir nunmal Computer verwenden, die Zeiger benötigen.

    Um meine Message etwas deutlicher auszudrücken: Gibt es irgendjemanden, den es interessiert, dass der Standard Zeiger nicht explizit vorschreibt?



  • [quote="Xin"]

    Tachyon schrieb:

    Xin schrieb:

    [...]

    D
    Der Standard schreibt nicht vor Pointer zu verwenden. Wir können hier gerne einen Einsteiger mit einer Philosophie über den C++-Standard beglücken, aber es wird ihm wesentlich mehr nutzen, wenn er weiß, dass Referenzen technisch über Pointer realisiert werden. Das erklärt ihm vergleichsweise einfach Referenzen, wie sich Referenzen verhalten und warum bei sizeof( int & ) ein bestimmtes Ergebnis zu erwarten ist.

    Genau, Wie sehr es dem Einsteiger nutzt, sieht man ja an der Frage des TOs (die Frage kommt hier regelmäßig): "Soll ich Pointer oder Referenzen benutzen? Ist ja technisch das Gleiche."
    sizeof(T &) verhält sich übrigens genau so, wie man es bei einem Alias von T erwarten würde und völlig anders als bei einem Pointer.



  • Tachyon schrieb:

    Technisch können sie gleich sein. Sind sie aber nicht in jedem Fall, und müssen sie auch nicht. Ich würde mich davon verabschieden, dass Refernzen was mit Pointern zu tun haben.

    Das nennt man dann quasi. Und ja, ich kenne den Standard. Und natürlich haben sie etwas miteinander zu tun, auch semantisch, sie zeigen beide auf etwas.



  • cooky451 schrieb:

    Tachyon schrieb:

    Technisch können sie gleich sein. Sind sie aber nicht in jedem Fall, und müssen sie auch nicht. Ich würde mich davon verabschieden, dass Refernzen was mit Pointern zu tun haben.

    Das nennt man dann quasi. Und ja, ich kenne den Standard. Und natürlich haben sie etwas miteinander zu tun, auch semantisch, sie zeigen beide auf etwas.

    Eine Refenrenz zeigt eben nicht. Eine Referenz ist das Etwas unter einem anderen Namen.



  • Tachyon schrieb:

    Eine Refenrenz zeigt eben nicht. Eine Referenz ist das Etwas unter einem anderen Namen.

    Doch, sie zeigt. Wenn eine Referenz aus dem Scope geht passiert gar nichts, und wenn das Objekt auf das sie zeigt aus dem Scope geht, ist jeder Zugriff auf die Referenz UB. (Außer bei den kleinen Sonderregeln für Lebensverlängerung.)



  • cooky451 schrieb:

    Tachyon schrieb:

    Eine Refenrenz zeigt eben nicht.

    Doch, sie zeigt.

    Gib ihm einfach sein Schäufelchen und gut ist - der Threadstarter hat sein Problem gelöst...



  • cooky451 schrieb:

    Tachyon schrieb:

    Eine Refenrenz zeigt eben nicht. Eine Referenz ist das Etwas unter einem anderen Namen.

    Doch, sie zeigt. Wenn eine Referenz aus dem Scope geht passiert gar nichts, und wenn das Objekt auf das sie zeigt aus dem Scope geht, ist jeder Zugriff auf die Referenz UB. (Außer bei den kleinen Sonderregeln für Lebensverlängerung.)

    Wirklich? Generell?



  • Vielleicht steh ich grad sowas von auf der Leitung... Kann mir bitte jemand eine Konstellation zeigen in der das referenzierte Objekt vor der Referenz aus dem Scope geht und die Referenz weiterlebt!? 😮



  • Swordfish schrieb:

    Vielleicht steh ich grad sowas von auf der Leitung... Kann mir bitte jemand eine Konstellation zeigen in der das referenzierte Objekt vor der Referenz aus dem Scope geht und die Referenz weiterlebt!? 😮

    Ich glaube er will auf eine const-ref auf ein temp. Objekt hindeuten. Wenn die Anweisung zu ende ist, ist das temp. Objekt ja aus dem Scope.



  • Swordfish schrieb:

    Vielleicht steh ich grad sowas von auf der Leitung... Kann mir bitte jemand eine Konstellation zeigen in der das referenzierte Objekt vor der Referenz aus dem Scope geht und die Referenz weiterlebt!? 😮

    Da lebt die Referenz weiter:

    int& foo()
    {
      int i = 0;
      return i;
    }
    

    Oder meinst du wo die Referenz das Objekt am Leben erhält?



  • Swordfish schrieb:

    Vielleicht steh ich grad sowas von auf der Leitung... Kann mir bitte jemand eine Konstellation zeigen in der das referenzierte Objekt vor der Referenz aus dem Scope geht und die Referenz weiterlebt!? 😮

    Evtl. könnte man da etwas mit Threads konstruieren...



  • Ich grabe den Thread einfach nochmal aus, weil mir etwas nicht so ganz klar ist:

    Folgender Quellcode:

    #include <iostream>
    
    class T {
    public:
    	T(const int a) {
    		m_A = a;
    	}
    
    	int _(int *a) {
    		return *a + m_A;
    	}
    private:
    	int m_A;
    };
    
    int main() {
    	T* test = new T(42);
    
    	int a = 100;
    
    	std::cout << test->_(&a) << std::endl;
    
    	delete test;
    
    	std::cout << test->_(&a) << std::endl;
    
    	return 0;
    }
    

    Ein Pointer zur Klasse T namens Test. Ausgabe:

    142
    -17891502
    

    Mir ist schonmal nicht ganz so klar, warum ich nach dem delete noch mit der Klasse arbeiten kann? Verstehe ich delete hier falsch?

    Aber was ganz merkwürdig ist:
    Ändere ich die Funktion _ wie folgt ab:

    int _(int *a) {
    		return *a + 100;
    	}
    

    Ist das Ergebnis:

    200
    200
    

    Huh? Warum? Das erste Ergebnis ist mir klar, das zweite aber weniger (in Anbetracht der oberen Ausgabe).



  • Undefiniertes Verhalten.



  • Lokart schrieb:

    Mir ist schonmal nicht ganz so klar, warum ich nach dem delete noch mit der Klasse arbeiten kann? Verstehe ich delete hier falsch?

    Oh, ein Outing als digitaler Nekrophiler...

    Die Klasse wird ausgelöscht, aber deswegen löst sie sich ja nicht gleich in Luft auf. Klar, liegen die Daten verbuddelt in der Erde, aber solange keine neuen Daten auf den Friedhof leben, bleibt die Datenleiche nunmal der Hauptbewohner dieser Speicherstelle.

    Du buddelst die Sachen jetzt wieder aus und da sich in der Zwischenzeit auf dem Speicherbereich auf den test zeigt, nichts Neues angesiedelt hat, poppst Du die Überreste lockerflockig durch die Methode _.

    Niemand hat ein Problem damit, wenn Du Existenzen zerstörst, aber Deine Methoden, sie nach ihrem Tod auch noch in Parameter zu pushen und poppen, sind unschön und wird von den gesellschaftlichen Normen nicht getragen.

    Du könntest Dein Opfer auch auslöschen, indem Du temp mit NULL reinitialisierst, damit Du jedesmal, wenn Du, wenn Dich mal wieder das Verlangen überkommt, den Hinweis 'Anwendung reagiert nicht mehr'. erhältst. :->



  • Klassen funktionieren anders als du denkst.

    Zu allererst muss klar werden, was passiert, wenn man eine Klasse, die im Freispeicher allokiert wurde, wieder freigibt.

    Das einzige, was allokiert wird, sind die Member-variablen/konstanten.
    Also praktisch in deinem Beispiel m_A .
    Diese liegen jetzt hintereinander im Freispeicher;
    wenn du nun die Instanz wieder zerstörst und den Speicher freigibst, dann wird der Destruktor für die Instanz und alle Member aufgerufen. Und der Speicher freigegeben.

    Die Funktionen sind dabei völlig unabhängig von der Instanz. Man kann sie sogar ohne Instanz aufrufen.
    Dabei wird lediglich der this -Parameter 0 sein. Und this zeigt einfach auf diese Membervariablen im Freispeicher.

    Wenn du jetzt die Funktion _ aufrufst, kannst du dir das so vorstellen:

    std::cout << _(&test, &a) << std::endl;///Sieht nachher definitiv *nicht* so aus!
    

    Da du in deiner zweiten Version nicht auf die Membervariablen zugreifst, wird er this-Zeiger ignoriert,
    und du kannst selbst mit einem Zeiger auf wilden Murks die Memberfunktion sauber und definiert aufrufen.

    Das einzige, was bei der ersten Funktion undefiniert ist, ist der Zugriff auf Membervariablen eines ungültigen Objekts - also eines, dass nicht erstellt wurde oder schon wieder zerstört wurde.

    P.S.:

    Wenn du in einer Memberfunktion eine Membervariable nutzt, wird davor vom Compiler (kann aber auch manuell gemacht werden, macht manchmal Sinn) ein this-> gestellt.



  • Sone schrieb:

    Wenn du jetzt die Funktion _ aufrufst, kannst du dir das so vorstellen:

    std::cout << _(&test, &a) << std::endl;///Sieht nachher definitiv *nicht* so aus!
    

    Es sieht so gar definitiv so aus. (auf die hier zwangsläufig folgende Diskussion, dass der Standard das so nicht vorgibt, werde ich nicht eingehen...)

    Schöne Erklärung, leider so wenig makaber, wo es sich doch derart aufdrängt ;-). 👍



  • Xin schrieb:

    Sone schrieb:

    Wenn du jetzt die Funktion _ aufrufst, kannst du dir das so vorstellen:

    std::cout << _(&test, &a) << std::endl;///Sieht nachher definitiv *nicht* so aus!
    

    Es sieht so gar definitiv so aus. (auf die hier zwangsläufig folgende Diskussion, dass der Standard das so nicht vorgibt, werde ich nicht eingehen...)

    Nein, ich meinte, von den Bezeichnern her sieht es nicht so aus 😃
    Die sind dann doch irgendwelche Buchstaben mit Nummern (ich dachte immer, Hashes)?

    Das der this-Parameter der erste ist, ist implementations-spezifisch, AFAIR.



  • Xin schrieb:

    Lokart schrieb:

    Mir ist schonmal nicht ganz so klar, warum ich nach dem delete noch mit der Klasse arbeiten kann? Verstehe ich delete hier falsch?

    Oh, ein Outing als digitaler Nekrophiler...

    Die Klasse wird ausgelöscht, aber deswegen löst sie sich ja nicht gleich in Luft auf. Klar, liegen die Daten verbuddelt in der Erde, aber solange keine neuen Daten auf den Friedhof leben, bleibt die Datenleiche nunmal der Hauptbewohner dieser Speicherstelle.

    Du buddelst die Sachen jetzt wieder aus und da sich in der Zwischenzeit auf dem Speicherbereich auf den test zeigt, nichts Neues angesiedelt hat, poppst Du die Überreste lockerflockig durch die Methode _.

    Niemand hat ein Problem damit, wenn Du Existenzen zerstörst, aber Deine Methoden, sie nach ihrem Tod auch noch in Parameter zu pushen und poppen, sind unschön und wird von den gesellschaftlichen Normen nicht getragen.

    Du könntest Dein Opfer auch auslöschen, indem Du temp mit NULL reinitialisierst, damit Du jedesmal, wenn Du, wenn Dich mal wieder das Verlangen überkommt, den Hinweis 'Anwendung reagiert nicht mehr'. erhältst. :->

    Danke, nicht nur für die Erklärung - ich musste lachen ;> Vor allem "Digitaler Nekrophiler" gibt mir grad alles. Ich suche schon längere Zeit n guten Blognamen; ich glaube das kam gerade in die Favoriten 😃

    Und danke für die restlichen Erklärungen. So ergibt das natürlich Sinn. Ich werde zukünftig darauf acht geben nicht mit Leichen zu arbeiten 🙂


Anmelden zum Antworten