Pointer vs. Reference



  • Lokart schrieb:

    Vor Aufruf von f4() weiß ich, dass ich a nicht mehr brauche. Wie gebe ich den Speicher nun frei?

    Etwa so?

    #include <iostream>
    
    typedef int UnmengeAnDatan;
    
    UnmengeAnDaten * f1() {
    	/* unmengen an quellcode, zig milliarden zeilen, unmengen an speicherverbrauch */
    	return new UnmengeAnDaten(1);
    }
    
    UnmengeAnDaten f2(UnmengeAnDaten const &a) {
    	return a * 2;
    }
    
    UnmengeAnDaten f3(UnmengeAnDaten const &a) {
    	return a * 3;
    }
    
    int f4() {
    	return 1024;
    }
    
    int main() {
    	// lade die unmengen an daten
    	UnmengeAnDatan * a = f1();  // Pointer
    
    	if( a )
    	{
    	  UnmengeAnDaten const & alias = *a;
    
    	  // die erste funktion die a haben will:
    	  std::cout << f2( alias ) << std::endl;
    
    	  // die zweite funktion die a haben will
    	  std::cout << f3( alias ) << std::endl;
    
    	  delete a;  // bei Arrays delete [] a, vielleicht eine f1_free( a )-Funktion
    	             // die das sicher weiß?
    	}
    
    	// eine vierte funktion, die nicht mit a arbeitet, folglich könnte ich den speicher
    	// der im moment noch von a belegt wird, doch freigeben?
    	std::cout << f4() << std::endl;
    
    	return 0;
    }
    

    Das Alias brauchst Du nicht, Da Du mit if(a) sicherstellst, dass a existiert. Du kannst also auch f2 und f3 mit (*a) füttern.

    const (siehe f2, f3) solltest Du immer dann verwenden, wenn Du die Daten, die referenziert werden nicht verändern möchtest.

    PS: bei f1 bekommst Du ggfs. eine Exception um die Ohren geschmissen... aber das ist ein anderes Thema. if( a ) aus Prinzip, wer weiß, was Du von f1 geliefert bekommst, vielleicht fängt f1 die Exception und liefert NULL?
    Ansonsten mag ich auch new( std::nothrow ), wenn ich auf die Exception gut verzichten kann...



  • @Lokart: Ich glaube, wir reden hier aneinander vorbei. Wenn du zig Milliarden Daten zu verwalten hast, dann stellt sich erst einmal die Frage, was das für Daten sind. Als nächstes stellt sich die Frage, von wem und wie sie zu verwalten sind. Werden diese Fragen richtig beantwortet, brauchst du dir keine Sorgen mehr um deinen Speicherplatz machen.

    An dieser Stelle sollte man auch mal klar stellen, für welchen Zweck new gedacht ist. new benutzt du, wenn du das Objekt über den Scope hinaus am Leben erhalten musst (siehe Xins Beispiel).



  • Okay, langsam leuchtet es mir ein. Erstmal Danke an Alle für die ausführliche Erklärung.

    Da ich gerade bisschen am Programmieren bin noch etwas über Vektoren, die mit dem Thema zwar nichts zu tun hat, aber wenn der Thread schon steht:

    Kann es sein, dass die folgende Art Elemente in einen Vektor einzutragen ein C++11 Feature ist?

    std::vector< int > b{ 1, 2, 3 ,4 };
    

    Irgendwie findet mein MSVC 2010 das nicht so knorke, obwohl die Initialisierung auf diese Art ja eigentlich machbar sein müsste?

    Und:
    Die Handhabung von mehrdimensionalen Vektoren (bzw. Vektoren von Vektoren) ist mir noch nicht so klar.

    std::vector< std::vector< int > > b;
    

    Bedeutet das b ein Vektor von Vektoren mit dem Typ int ist - aber wie kriege ich da jetzt Elemente rein?
    Ich will nämlich einige Dinge für mein aktuelles Teil in einem Vektor speichern - bestehend aus einem Integer und "einem dazugehörigen Integer" (gibts wohl in PHP Arrays mit Keys => Values). Bestenfalls sogar noch unterteilt in Kategorien, womit ich eigentlich bei "einem Vektor von Vektoren von Vektoren" wäre, aber ich scheitere ja schon an der zweiten Dimension.
    Damit man sich das besser vorstellen kann hier eine kleine "Verdeutlichung":

    Person_1 =>
    	Autos =>
    		bmw
    		audi
    	Frauen =>
    		hannah
    		silke
    		christina
    	[...]
    		[...]
    

    Sprich Hauptelement ist "Person_1", zu Person_1 gehört "Autos" und "Frauen". Die Werte von "Autos" und "Frauen" sind eben die oben stehenden Sachen.



  • Lokart schrieb:

    Kann es sein, dass die folgende Art Elemente in einen Vektor einzutragen ein C++11 Feature ist?

    std::vector< int > b{ 1, 2, 3 ,4 };
    

    Irgendwie findet mein MSVC 2010 das nicht so knorke, obwohl die Initialisierung auf diese Art ja eigentlich machbar sein müsste?

    Ja das ist ein C++11 Feature. Selbst in MSVS 2012 haut das noch nicht hin.

    Lokart schrieb:

    Die Handhabung von mehrdimensionalen Vektoren (bzw. Vektoren von Vektoren) ist mir noch nicht so klar.

    std::vector< std::vector< int > > b;
    

    Erstmal: Da du ja offensichtlich schon C++11 nutzt, ist so ein Konstrukt nicht mehr so oft nötig. std::vector<std::vector> bedeutet nämlich, dass du eine Liste von Listen hast. Das ist nicht nötig, da die innere Dimension meist eine bekannte feste Größe hat und demnach nicht dynamisch sein muss. Ergo nicht std::vector<std::vector> sondern std::vector<std::array>

    Lokart schrieb:

    Bedeutet das b ein Vektor von Vektoren mit dem Typ int ist - aber wie kriege ich da jetzt Elemente rein?

    Na, wenn der Container einen Container will, dann musst du ihm einen geben:

    vecor< vector<int> > aussen;
    vector<int> innen = { 1, 2, 3, 4, 5 };
    aussen.push_back( innen );
    cout << endl << aussen[0][0];
    

    Lokart schrieb:

    bestehend aus einem Integer und "einem dazugehörigen Integer" (gibts wohl in PHP Arrays mit Keys => Values). Bestenfalls sogar noch unterteilt in Kategorien, womit ich eigentlich bei "einem Vektor von Vektoren von Vektoren" wäre, aber ich scheitere ja schon an der zweiten Dimension.
    Damit man sich das besser vorstellen kann hier eine kleine "Verdeutlichung":

    Person_1 =>
    	Autos =>
    		bmw
    		audi
    	Frauen =>
    		hannah
    		silke
    		christina
    	[...]
    		[...]
    

    Sprich Hauptelement ist "Person_1", zu Person_1 gehört "Autos" und "Frauen". Die Werte von "Autos" und "Frauen" sind eben die oben stehenden Sachen.

    Aso, das willst du also. Ganz schön viele Frauen hat so eine Person :D. Ich hab da mal ein Versuch gemacht. Herausgekommen ist das:

    #include <iostream>
    #include <map>
    #include <string>
    #include <vector>
    using namespace std;
    
    enum Werte { Autos, Frauen };
    
    int main()
    {
    	map< string, map<Werte,vector<string>> > Personen;
    
    	// Zu Person_1
    	{
    		map<Werte,vector<string>> zu_person_1;
    
    		vector<string> autos;
    		autos.push_back("bmw");
    		autos.push_back("audi");
    		zu_person_1[Autos] = autos;
    
    		vector<string> frauen;
    		frauen.push_back("hannah");
    		frauen.push_back("silke");
    		frauen.push_back("christina");
    		zu_person_1[Frauen] = frauen;
    
    		Personen["Person_1"] = zu_person_1;
    	}
    
    	// Testausgaben
    	{
    		cout << endl << "Autos: ";
    		for(auto& a : Personen["Person_1"][Autos])
    			cout << a << ' ';
    
    		cout << endl << "Frauen: ";
    		for(auto& f : Personen["Person_1"][Frauen])
    			cout << f << ' ';
    	}
    }
    


  • Vielen Dank! 🙂



  • cooky451 schrieb:

    Lokart schrieb:

    Es wird langsam kompliziert; zwar sind sie theoretisch das Selbe (mit ein paar Ausnahmen), ich sollte aber nicht beachten, dass sie etwas miteinander zu tun haben. Das Pointer-Reference-Paradoxon? 😛

    Technisch sind sie quasi gleich, semantisch nicht.

    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.

    [quote:55ebf8ce7b="ISO/IEC
    14882/2011 8.3.2§4"]It is unspecified whether or not a reference requires storage
    Das sagt eigentlich deutlich, dass bei Referenzen technisch nicht zwangsläufig mit Zeigern realisiert werden müssen...

    PS: Achso, semantisch ist natürlich der folgende Absatz viel interessanter:
    [quote:55ebf8ce7b="ISO/IEC
    14882/2011 8.3.2§5"]There shall be no references to references, no arrays of references, and no pointers to references. The
    declaration of a reference shall contain an initializer [...] A reference shall be initialized to refer to a valid object
    or function. [ Note: in particular, a null reference cannot exist in a well-defined program, because the only
    way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer,
    which causes undefined behavior.[...]
    Das zeigt schon sehr deutlich die Unterschiede auf.



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


Anmelden zum Antworten