Stringklasse mit Reference Counting (aus More Effective C++)



  • Ich habe gerade das Kapitel29 mit der Referenzzählung für eine Stringklasse erreicht,
    nun hat Scott Meyers hier ein Beispiel welches für mich keinen Sinn ergibt.
    Er hat folgende Stringklasse (bis zu dieser Stelle hat die Klasse noch nicht mehr):

    class String {
    	private:
    		struct StringValue {
    			int refCount; // Referenzzähler
    			char *data; // Der eigentliche String
    			StringValue (const char *initValue);
    			~StringValue ();
    		};
    
    		StringValue *value;
    	public:
    		String (const char *initValue = "");
    		String (const String &rhs);
    };
    
    String::String (const char *initValue)
    	: value (new StringValue (initValue))
    {}
    
    String::String (const String &rhs)
    	: value (rhs.value) {
    
    		++value->refCount;
    }
    
    String::~String () {
    
    	if (--value->refCount == 0)
    		delete value;
    }
    
    String::StringValue::StringValue (const char *initValue)
    	: refCount (1) {
    
    		data = new char[strlen (initValue) + 1];
    		strcpy (data, initValue);
    }
    
    String::StringValue::~StringValue () {
    	delete [] data;
    }
    

    Laut Scott Meyers sieht grafisch folgender Code:

    String s1 ("More Effective C++");
    String s2 = s1;
    

    so aus:

    s1 ------------|
                   -- >---------->(2)--------->"More Effective C++"
    s2 ------------|
    

    Kann mir jdm. erklären wie das bei dem obigen Code möglich sein soll 😕



  • bis zum Copy on write wird zwischen zwei oder mehreren Klassen, nur eine einzige StringValue Struktur geshared. Das gehört zu den Lazy Evaluation Techniken.



  • SirLant schrieb:

    Kann mir jdm. erklären wie das bei dem obigen Code möglich sein soll

    Er erklärt es schon recht ausführlich, wenn dus noch nicht verstanden hast, werde bitte mal genauer.

    Er sagt auch ausdrücklich, dass es nicht auf den String ankommt (Würdest du jetzt einen String s3 anlegen, ebenfalls mit "More Effective C++", würde der counter nicht erhöht).



  • Der Sinn von Reference Counting ist mir klar, mir ist auch klar, was die Zeilen
    da oben bedeuten, mir ist nur nicht klar, wie mit obigem Code, das grafisch
    dargestellte möglich sein soll



  • mir ist auch klar, was die Zeilen da oben bedeuten, mir ist nur nicht klar, wie mit obigem Code, das grafisch dargestellte möglich sein soll

    Das ist aber jetzt ein Widerspruch. Meiner Meinung nach sind nicht so extrem viele komplexe Dinge dort verarbeitet. Alles irgendwie Standard (Konstruktor, Copykonstruktor, Zeiger), nur die Art und Weise wie es benutzt wird, ist interessant.

    Der Zeiger in der Struktur zeigt auf das char*, also den eigentlichen und einzigen String , und die int Variable wird, sobald der Copykonstruktor aufgerufen wird, erhöht, und beim Destruktor wird dekrementiert.



  • Das einzige nicht ganz Offensichtliche hier ist IMHO, dass "String s2 = s1" den Kopierkonstruktor aufruft, auch wenn es wie eine Zuweisung aussieht. Aber das wurde im Buch bestimmt schon beschrieben?



  • Ist doch easy 😉

    SirLant schrieb:

    class String {
    	private:
    		struct StringValue {
    			int refCount; // Referenzzähler
    			char *data; // Der eigentliche String
    			StringValue (const char *initValue);
    			~StringValue ();
    		};
    		
    		StringValue *value;
    	public:
    		String (const char *initValue = "");
    		String (const String &rhs);
    };
    
    String::String (const char *initValue) //char*, kein RC möglich
    	: value (new StringValue (initValue))
    {}
    
    String::String (const String &rhs) //ah, eine kopie - also RC
    	: value (rhs.value) { //zeiger merken
    //man beachte: KEINE Kopie, nur zeiger merken
    	
    		++value->refCount; //refcount erhöhen
    }
    
    String::~String () {
    	
    	if (--value->refCount == 0) //RC verringern
    		delete value; //wenn leer - dann adieu
    }
    
    String::StringValue::StringValue (const char *initValue) //ein neues objekt
    	: refCount (1) { //RC muss 1 sein ;)
    	//string anlegen
    		data = new char[strlen (initValue) + 1];
    		strcpy (data, initValue);
    }
    
    String::StringValue::~StringValue () {
    	delete [] data;
    }
    

    ich hoffe es ist jetzt klar?



  • Nein davon steht da nichts, ich ging davon aus, der ruft den built-in Zuweisungsoperator auf.
    Aber auch, wenn er den Copy Ctor aufruft, wie kommt es da zu dem kopieren der Adresse
    von s1 in s2?
    Beim Aufruft des Copy Ctors wird ja folgendes gemacht:
    Initialisierungsliste: value (rhs.value)
    -Aufruf des StringValue Ctors und dieser kopiert den string ja in den mit new
    allokierten Speicherbereich und setzt refCount auf 1.
    -Copy Ctor von String erhöht, dann den Referenzzähler noch um 1 wodurch er auf 2 steht

    Wo wird hier, denn die Adresse von s1 an s2 zugewiesen?

    Edit:
    Shade, die Zeile die für dich Sonnenklar ist verstehe ich nicht:

    : value (rhs.value) { //zeiger merken
    //man beachte: KEINE Kopie, nur zeiger merken
    

    Wie kann er sich hier den Zeiger einfach merken ?

    Edit:
    Achso ich glaub ich habe es, hier wird der built-in Copy Ctor von StringValue
    aufgerufen der einfach eine Flache Kopie erzeugt, richtig ?



  • SirLant schrieb:

    Nein davon steht da nichts, ich ging davon aus, der ruft den built-in Zuweisungsoperator auf.

    siehe Humes FAQ - direct initialization versus copy initialization

    Aber auch, wenn er den Copy Ctor aufruft, wie kommt es da zu dem kopieren der Adresse
    von s1 in s2?
    Beim Aufruft des Copy Ctors wird ja folgendes gemacht:
    Initialisierungsliste: value (rhs.value)
    -Aufruf des StringValue Ctors und dieser kopiert den string ja in den mit new
    allokierten Speicherbereich und setzt refCount auf 1.
    -Copy Ctor von String erhöht, dann den Referenzzähler noch um 1 wodurch er auf 2 steht

    schau dir den code mit meinen kommentaren mal an - dann sollte es klar werden.

    beachte: value ist ein Zeiger auf StringValue Objekt - deshalb ist value(rhs.value) nur eine Zeiger-Kopiererei.

    Die StringValue Objekte sind die, die geshared werden 😉



  • SirLant schrieb:

    Achso ich glaub ich habe es, hier wird der built-in Copy Ctor von StringValue
    aufgerufen der einfach eine Flache Kopie erzeugt, richtig ?

    nein, sowohl value als auch rhs.value sind nur Zeiger - es wird also der Zeiger kopiert.



  • SirLant schrieb:

    Wie kann er sich hier den Zeiger einfach merken ?

    Edit:
    Achso ich glaub ich habe es, hier wird der built-in Copy Ctor von StringValue
    aufgerufen der einfach eine Flache Kopie erzeugt, richtig ?

    es wird KEIN Ctor aufgerufen, wie denn auch? Es ist ein Zeiger! Die Kopie des Zeigers der auf die StringValue Struktur zeigt, wird gespeichert.



  • Und durch was wird dieser kopiert? Ist das einfach ein value = rhs.value; ?



  • SirLant schrieb:

    Und durch was wird dieser kopiert? Ist das einfach ein value = rhs.value; ?

    Keine Ahnung was du meinst.

    Ich glaube du hast gerade ne kleine Denkblockade. Am besten du lässt das Thema mal kurz in Ruhe und schaust dir den Code in einer Stunde oder so nochmal an.

    Denn ich weiss, dass du genug wissen hast, um den Code zu verstehen - also mach mal ne kleine Pause, dann siehst du wieder den Wald (trotz der Bäume)

    Und nicht vergessen: value ist nur ein Zeiger, somit ist value=rhs.value eine Zuweisung von Zeiger an Zeiger.



  • Hallo,

    SirLant schrieb:

    Und durch was wird dieser kopiert? Ist das einfach ein value = rhs.value; ?

    richtig, über die "initializer list" des Copy-Konstruktors:

    [cpp]
    String::String (const String &rhs)
    : value (rhs.value)
    [/cpp]

    Initialisierung über "initializer list".

    MfG



  • Jetzt hat sich die Blockade gerade gelöst, ist ja eigentlich total logisch 🤡

    Danke 🙂



  • SirLant schrieb:

    Und durch was wird dieser kopiert? Ist das einfach ein value = rhs.value; ?

    sei mir nicht böse, aber bevor man Mayers liest, sollte man schon wissen, was ein Element-Initialisierer ist.


Anmelden zum Antworten