Eigene Stringklasse, char* dynamisch vergrößern



  • Guten Tag,

    ich versuche gerade eine eigene Stringklasse zu implementieren. Dafür habe ich eine Objektvariable private:char* str;. Bei meinem Konstruktor möchte ich nun str dynamisch verändern, sodass es x groß wird.

    Str::Str(char const* s){
    
    	int len = 0;
    	for (len;s[len]!= 0;len++);
    
    	str = (char*) realloc(str,len*sizeof(char));
    
    	for(int i=0;i<len;i++){
    		str[i]=s[i]; 
    	}
    }
    

    Habe vorher versucht len mit sizeof(s)/sizeof(char) zu ermitteln, dabei kam leider Quatsch bei raus, die len jetzt gibt jedenfalls eine richtige Länge an.
    Dazu habe ich realloc gefunden, leider scheint es noch nicht ganz richtig zu sein.



  • realloc versucht die Größe eines bereits allokierten Speicherbereichs zu vergrößern. In deinem Code zeigt jedoch str ins Nirvana weil es nicht initialisiert wurde. Das sollte ab und zu zum Absturz führen.
    Für Längen von C-Strings gibt es strlen und zum kopieren strcpy obwohl in deinem Fall eher memcpy angesagt wäre. Ach was rede ich, std::copy. Nicht so viel die bösen C-Funktionen benutzen. std:string speichert die Größe, vielleicht willst du das auch machen. Außerdem solltest du dir überlegen, ob du nicht new[] und delete[] statt malloc, realloc und free benutzen willst.


  • Mod

    Schon einmal die Dokumentation von realloc gelesen? Welchen Wert hat denn str, wenn du es an realloc uebergibst?

    Ich hoffe, ich habe jetzt deine Frage beantwortet, denn

    leider scheint es noch nicht ganz richtig zu sein.

    ist keine brauchbare Fehlerbeschreibung. Ich kann mir noch zig andere Dinge vorstellen, die du falsch gemacht haben koenntest (manuelle Speicherverwaltung ist recht fehleranfaellig und du scheinst es das erste Mal zu machen).



  • Gibt es irgendeinen Grund nicht std::vector zu benutzen?

    private: std::vector<char> str;
    //...
    Str::Str(char const* s) : str(s, s + std::strlen(s) + 1) {}
    

    Wenn es nicht vector sein soll, wäre die nächste Wahl unique_ptr .

    private:
      std::unique_ptr<char[]> str;
      std::size_t size;
    //...
    Str::Str(char const* s) : Str(s, s + std::strlen(s)) {}
    //...
    Str::Str(char const *begin, char const *end) : size(std::distance(begin, end)) {
      str.reset(new char[size + 1]); //ein Byte mehr für Nullterminierung
      std::copy(begin, end, str.get());
      str.get()[size] = '\0'; //Nullterminierung für .c_str(), das du wahrscheinlich haben möchtest
    }
    
    Str::~Str() {
      //nichts, denn das Freigeben macht unique_ptr für dich (wie vector es auch täte)
    }
    

  • Mod

    TyRoXx schrieb:

    Gibt es irgendeinen Grund nicht std::vector zu benutzen?

    Ein string kann durchaus recht andere Anforderungen an die Allokationsstrategie haben, als ein std::vector. Darum ist in der Standardbibliothek ein std::string intern auch kein std::vector. Zumindest bei den ueblichen Implementierungen. Ein std::vector wuerde durchaus die Anforderungen an std::string erfuellen, std::string-Implementierungen sind in der Regel trotzdem anders, weil die Anforderungen an std::string (bewusst!) nicht so restriktiv sind wie an std::vector. (Vermutlich hat der TE aber nicht so weit gedacht 🙂 )

    Ob der std::string selber die Aufgabe hat, die Ressourcen zu verwalten oder ob er dies an eine Unterklasse auslagern sollte, darueber laesst sich sicherlich streiten. Der string ist meiner Meinung nach ein Grenzfall zwischen Ressourcenverwalter und einer Klasse, bei der es eher um die sonstige Funktionalitaet geht.



  • SeppJ schrieb:

    Ein std::vector wuerde durchaus die Anforderungen an std::string erfuellen, std::string-Implementierungen sind in der Regel trotzdem anders, weil die Anforderungen an std::string (bewusst!) nicht so restriktiv sind wie an std::vector.

    Welche Anforderung zum Beispiel?

    Umgekehrt erfüllt vector übrigens nicht die Bedingungen für string, weil der Nullterminator fehlt.



  • Ok, swap.


  • Mod

    enable_if<c++11> schrieb:

    Ok, swap.

    Genau. Vor C++11 gab's noch mehr Unterschiede, z.B. wann ein Iterator ungueltig werden darf (bei string gab's da mehr Faelle). Diese wurden aber ebenfalls bewusst entfernt, da sie im alten Standard waren, um die Copy-on-Write-Optimierung zu ermoeglichen. Diese Optimierung ist jedoch in Ungnade gefallen, weshalb man den Standard umformuliert hat, um sie nicht mehr zu ermoeglichen.
    Der Unterschied beim swap erlaubt es einem std::string beispielsweise, eine short-string-Optimierung einzusetzen, etwas, dass ein std::vector nicht kann.

    enable_if<c++11> schrieb:

    Umgekehrt erfüllt vector übrigens nicht die Bedingungen für string, weil der Nullterminator fehlt.

    Das ist keine allgemeine Anforderung an die Eigenschaften eines std::string (der muss nicht nullterminiert sein), sondern an std::string::c_str(), welche ein vector ohnehin nicht hat. Ein String auf Basis von vector muesste diese Methode ohnehin neu implementieren und kann dann ein Nullzeichen anhaengen (oder einfach staendig ein Nullzeichen am Ende halten).



  • SeppJ schrieb:

    enable_if<c++11> schrieb:

    Umgekehrt erfüllt vector übrigens nicht die Bedingungen für string, weil der Nullterminator fehlt.

    Das ist keine allgemeine Anforderung an die Eigenschaften eines std::string (der muss nicht nullterminiert sein), sondern an std::string::c_str(), welche ein vector ohnehin nicht hat. Ein String auf Basis von vector muesste diese Methode ohnehin neu implementieren und kann dann ein Nullzeichen anhaengen (oder einfach staendig ein Nullzeichen am Ende halten).

    s.c_str()==s.data() und c_str noexcept und invalidiert keine Iteratoren, daher müssen alle anderen Methoden das Nullzeichen entweder reinschreiben oder Speicher dafür reservieren. Ein String auf Basis von vector müsste daher fast alle Methoden neu implementieren, nicht nur c_str.


  • Mod

    der muss nicht nullterminiert sein

    Doch. str[str.size()] == 0 , und zwar immer. Außerdem zeigt data() / c_str() direkt auf das interne Array. (Seit C++11, siehe §21.4.7.1 und §21.4.5)

    sondern an std::string::c_str(), welche ein vector ohnehin nicht hat.

    Nein, aber ein vector hat data() , und das ist dann verschieden.


  • Mod

    Und was aendert das? Man muss ohnehin neue Methoden schreiben, auch neue Konstruktoren. Die fuegen dann eine Null am Ende ein. Das aendert nichts da dran, dass man eine std::string absolut standardkonform mit einem std::vector als internen Ressourcenhalter implementieren koennte. Dem vector vorzuwerfen, dass er keine automatische Nullterminierung einfuegt ist, wie ihm vorzuwerfen, dass er kein substr oder keinen operator<= anbietet. Das geht voellig am Punkt der Speicherverwaltung vorbei sondern ist Funkionalitaet der Stringklasse und muss von dieser implementiert werden.

    Umgekehrt ist es unmoeglich, einen vector intern irgendwie mit einem std::string zu implementieren (ueber irgendwelche wilden reinterpret_casts und placements news), da der string nicht die Anforderungen an vector::swap erfuellen kann.

    (Irgendwelche Custom-Allocators lasse ich bei der Diskussion mal aussen vor. Wir reden hier von der normalen Verwendungsweise, bei denen sowohl vector als auch string letztendlich irgendwie ein malloc/new benutzen)



  • SeppJ schrieb:

    Umgekehrt ist es unmoeglich, einen vector intern irgendwie mit einem std::string zu implementieren (ueber irgendwelche wilden reinterpret_casts und placements news), da der string nicht die Anforderungen an vector::swap erfuellen kann.

    Mit unique_ptr<string> trivial möglich.


  • Mod

    Man kann sowieso nicht vector über basic_string implementieren, weil basic_string nur PODs halten kann (die kein Array sind). Solche Typen sind in Standardklausel 21 als char-like Typen beschrieben.


Anmelden zum Antworten