CStringW zu std:string



  • Hallo,

    ich weiß aktuell nicht weiter.. ich muss ein CStringW (Unicode) in einen std::string const& encoded_string umwandeln.

    Der CString ist ein Datenstream Base64 codiert der auch 50-300mb groß sein kann. Deswegen sollte ich beim umwandeln so wenig wie möglich Speicher verbrauchen.

    Ich habe das ganze jetzt erst in CStringA umgewandelt und anschließend in std::string um das ganze dann in einen const sdt::string umzuwandeln.

    --> Ich kopiere 3 mal, das ist richtig schlecht. Im Arbeitsspeicher hab ich dann mal schnell n haufen Datenmüll. Könnt ihr mir weiter helfen?



  • Warum speicherst Du überhaupt Base64-kodierte Daten als Wide-String, wo Base64 doch nur ASCII-Zeichen (und selbst davon nur eine Teilmenge) verwendet?

    So oder so, wieso willst Du von Deinem Base64-kodierten Wide-String auf einen** std:string **(ich nehme mal an wieder Base64-kodierte) konvertieren? Ist ist ja immer noch reichlich Overhead.

    Könntest Du den Base64-String nicht direkt parsen und in Binär-Daten umwandeln?

    Base64 kann pro Zeichen 6-Bit Nutzdaten unterbringen, was bei 16-Bit pro Zeichen (Wide-String) einem Overhead von ~63% entspricht. Bei 8-Bit pro Zeichen (ANSI-String) sind es aber immer noch 25%.

    ______

    BTW, eine Umwandlung von** CStringW nach std::string **ohne unnötiges Kopieren könnte wie folgt aussehen:

    static const std::string *convert(const CStringW &input)
    {
    	const int len = input.GetLength();
    	std::string *outputBuffer = new std::string();
    	outputBuffer->reserve(len);
    	char *temp = (char*) alloca(MB_CUR_MAX);
    	for(int i = 0; i < len; i++)
    	{
    		wctomb(temp, input.GetAt(i));
    		(*outputBuffer) += temp[0];
    
    	}
    	return outputBuffer;
    }
    


  • Naja wenn er sagt er hat nen CStringW dann wird er sich das vermutlich nicht ausgesucht haben. Von daher macht die Frage wenig Sinn warum er Base64 als Wide-String bekommt. Ist halt so.

    Und wozu die Frickelei mit Zeiger und new ? Schonmal was von RVO/NRVO gehört? 😉
    (Bzw. wenn man sich darauf nicht verlassen will, dann macht man halt nen Output-Parameter.)

    Und das wctomb könnte man auch weglassen, wenn garantiert ist dass immer nur gültige Base64-Zeichen enthalten sind (und man auf Support von diversen exotischen Codepages verzichtet).
    So wie du es machst werden z.B. sowieso keine echten "mb" Codepages unterstützt, da du nur ein "b" pro "w" erlaubst.

    @lulu32
    Musst du wirklich nen std::string 'draus machen? Die speicherfreundlichste Variante wäre nämlich die Base64 Dekodierung gleich mit dem CStringW zu machen. Das wäre meine Empfehlung, sofern es eben möglich ist.



  • Vielen Dank für eure Antworten.

    Kurze Verständnisfrage, das Projekt ist auf Unicode eingestellt. Kann ich dann auch CStringA typsafe benutzen?

    Ich habe mir den restlichen Code angesehen. Beginnen tut das ganze mit const char* wird dann in einen CStringW umgewandelt (durch copykonstruktor) "CstringW cstrData(cData);" Wo die erste Kopie ist.

    Dann wird der CStringW zerlegt und einige Informationen geholt. Wichtig ist hier wohl die delete Funktion und Find der CString Klasse.

    Anschließend wird mir ein Teil des Strings, Base64 codiert übergeben. Diesen muss ich jetzt umwandeln. Dafür habe ich eine Funktion die einen "std::string const& str" möchte.

    Am besten wäre es doch wenn ich den const char* vom Anfang nehe und diesen in einen std:String umwandle und den dann zerlege. Ich hoffe nur das std:String genau so cool ist wie CString 😉



  • lulu32_o schrieb:

    Kurze Verständnisfrage, das Projekt ist auf Unicode eingestellt. Kann ich dann auch CStringA typsafe benutzen?

    Ja, Du kannst mit** CStringA arbeiten, auch in einem Unicode Projekt. Sowohl CStringW als auch CStringA sind lediglich Spezialisierungen von CStringT . Und CString (ohne A oder W) ist ein Alias für entweder CStringW oder CStringA , je nach dem, wie dein Projekt eingestellt ist. Wenn Du also möchtest, dass Dein Code je nach Projekt-Einstellung einen Wide- oder ANSI-String benutzt, dann schreibe CString (meistens die richtige Wahl). Wenn Du aber explizit einen Wide- bzw. ANSI-String haben willst, schreibe CStringW bzw. CStringA **.

    lulu32_o schrieb:

    Ich habe mir den restlichen Code angesehen. Beginnen tut das ganze mit const char* wird dann in einen CStringW umgewandelt (durch copykonstruktor) "CstringW cstrData(cData);" Wo die erste Kopie ist.

    Und dieser Code ist fest vorgegeben? Ansonsten würde ich hier die Deep-Copy, die im Konstruktor des CString-Objekts passiert, einfach ganz raus nehmen - es sei denn, es wird tatsächlich eine Kopie der Daten benötigt. Eventuell kann man ja einfach den** const char* Pointer komplett durch reichen und sich das CString-Objekt sparen? Auf jeden Fall würde ich aber CStringW durch CStringA **ersetzen, sofern klar ist, dass ohnehin nur ASCII-Zeichen bzw. sogar nur Base64-Zeichen vorkommen werden.

    lulu32_o schrieb:

    Anschließend wird mir ein Teil des Strings, Base64 codiert übergeben. Diesen muss ich jetzt umwandeln. Dafür habe ich eine Funktion die einen "std::string const& str" möchte.

    Wenn die Funktion, die Du aufrufen möchtest, einen const-Referenz auf einen** std::string verlangt, kannst Du an der Stelle einfach Deinen std::string als Parameter übergeben. Das nur, weil Du weiter oben meintest, Du müsstest deinen std::string noch einmal durch Umkopieren in einen const std::string **"umwandeln", was definitiv nicht notwendig sein sollte.



  • hustbaer schrieb:

    Und wozu die Frickelei mit Zeiger

    War ja nur ein Beispiel, wie man hier ganz grundsätzlich vorgehen kann, und nicht die Empfehlung, dass er den Code 1:1 in sein Projekt kopieren soll...

    hustbaer schrieb:

    und new ? Schonmal was von RVO/NRVO gehört? 😉

    Nein, noch nie :p

    hustbaer schrieb:

    Und das wctomb könnte man auch weglassen, wenn garantiert ist dass immer nur gültige Base64-Zeichen enthalten sind

    Wenn man sich tatsächlich darauf verlassen kann, dann ja.

    hustbaer schrieb:

    So wie du es machst werden z.B. sowieso keine echten "mb" Codepages unterstützt, da du nur ein "b" pro "w" erlaubst.

    In der Standard-Locale ist MB_CUR_MAX zwar gleich 1, aber darauf sollte man sich besser nicht verlassen und das Ausgabe-Array immer entsprechend des aktuellen MB_CUR_MAX allokieren, da es sonst zu einem Puffer-Überlauf kommen könnte.

    Dass wir später nur das erste Byte aus dem Ausgabe-Array übernehmen sollte kein Problem sein, da praktisch alle Multibyte-Kodierungen zu ASCII abwärts-kompatibel sind, d.h. alle ASCII-Zeichen werden mit einem einzigen Byte und mit dem selben Bit-Mutser wie im ASCII-Standard dargestellt (Beispiel UTF-8: Jeder ASCII-kodierte Text ist automatisch auch ein 100% gültiger UTF-8 Text; In Umgekehrter Richtung gilt das natürlich nur, wenn der UTF-8 Text keine Zeichen enthält, die in ASCII nicht darstellbar sind).



  • Danke für eure Antworten,

    ich habe es der Einfachheit halber auf CStringA umgebaut.

    Um es dann ohne kopieren auf std::String zu bekommen, habe ich folgendes programmiert:

    const int len = cstrSource.GetLength();
        std::string *outputBuffer = new std::string();
    
    	for(int i = 0; i < len; i++)
        {   
    		(*outputBuffer) += cstrSource.GetAt(i);
        }
    

    Ich habe jetzt aber noch nicht 100% verstanden was da eigentlich passiert? Ich hole einen Character aus dem CString und kopiere den in den Pointer? Oder wird da nur eine Referenz gesetzt? Wie sieht das intern aus?



  • lulu32_0 schrieb:

    Danke für eure Antworten,

    ich habe es der Einfachheit halber auf CStringA umgebaut.

    Um es dann ohne kopieren auf std::String zu bekommen, habe ich folgendes programmiert:

    const int len = cstrSource.GetLength();
    std::string *outputBuffer = new std::string();
    
    for(int i = 0; i < len; i++)
    {   
        (*outputBuffer) += cstrSource.GetAt(i);
    }
    

    Ich habe jetzt aber noch nicht 100% verstanden was da eigentlich passiert? Ich hole einen Character aus dem CString und kopiere den in den Pointer? Oder wird da nur eine Referenz gesetzt? Wie sieht das intern aus?

    Falls "cstrSource" ein** CStringW **ist:

    Du allokierst zunächst einen neuen leeren** std::string . Anschließend fügst Du dann den CStringW "cstrSource" Zeichen für Zeichen via += Operator an Deinen std::string **an.

    Die einzelnen Zeichen, die Du Dir von Deinem** CStringW per GetAt() abholst, werden dabei einfach von wchar_t auf char "abgeschnitten", da Du auf die wctomb() **Variante verzichtet hast.

    _______________

    Falls "cstrSource" hingegen ein** CStringA **ist, dann könntest Du es doch gleich so machen:

    std::string *outputBuffer = new std::string(cstrSource.GetBuffer());
    
    std::string outputBuffer(cstrSource.GetBuffer());
    


  • Irgendwie scheint es nur so zu funktionieren:

    CStringW hellocw ( _T("Hello World!") );
    CStringA helloca ( hellocw );
    string hellos ( helloca.GetString() );
    


  • EOP schrieb:

    Irgendwie scheint es nur so zu funktionieren:

    CStringW hellocw ( _T("Hello World!") );
    CStringA helloca ( hellocw );
    string hellos ( helloca.GetString() );
    

    Nö, geht auch so:

    CStringW hellocw (L"Hello World!");
    const int len = hellocw.GetLength();
    std::string hellos;
    hellos.reserve(len);
    for(int i = 0; i < len; i++)
    {
    	hellos += (char) hellocw.GetAt(i);
    }
    printf("RESULT: \"%s\"\n", hellos.c_str());
    

    Immer vorausgesetzt, in "hellocw" kommen tatsächlich nur ASCII-Zeichen vor - sonst bekommst da Blödsinn raus 😉



  • Viele Wege führen nach Rom.

    Ob das aber Sinn macht die ganze Konvertierung zu Fuß zu programmieren. Bin mir nicht so sicher.



  • EOP schrieb:

    Viele Wege führen nach Rom.

    Ob das aber Sinn macht die ganze Konvertierung zu Fuß zu programmieren. Bin mir nicht so sicher.

    Naja, den** CStringW zuerst in einen CStringA zu kopieren, nur um ihn dann ein weiteres mal in einen std::string **zu kopieren, erscheint mir aber irgendwie wenig sinnvoll 😕

    Außerdem kann der Konstruktor von** CStringA , der einen CStringW als Argument annimmt, intern letztendlich auch nichts anderes tun, als den Eingabe-String Zeichen für Zeichen von wchar_t nach char **zu konvertieren.

    Da kann man diese Konvertierung auch direkt von** CStringW nach std::string **implementieren und sich mindestens eine überflüssige Deep-Copy sparen...



  • Ende der 80er hab ich auch den TurboC code als .asm ausgeben lassen und den code dann händisch optimiert.

    Braucht man das heutzutage noch? Eher nein wenn man nicht gerade ne Marsmission oder ne Herzpumpe programmiert.

    Also wieso sollte ich das zum 100000sten Mal selber programmieren wenn es auch einfacher geht?

    EDIT:
    Wenn du gleich nach ASCII umwandelst und base64 decodierst sparst du eine Menge an Speicher.
    50% gegenüber UNICODE und ungefähr nochmal 4/5 nach dem base64 decodieren.

    EDIT #2:
    Quatsch, du sparst natürlich 1/5 und nicht 4/5.



  • EOP schrieb:

    Ende der 80er hab ich auch den TurboC code als .asm ausgeben lassen und den code dann händisch optimiert.

    Braucht man das heutzutage noch? Eher nein wenn man nicht gerade ne Marsmission oder ne Herzpumpe programmiert.

    Also wieso sollte ich das zum 100000sten Mal selber programmieren wenn es auch einfacher geht?

    Wieso sollte man Daten nicht unnötig 2x hin und her kopieren, wenn es sich auch relativ leicht vermeiden lässt? Die Frage beantwortet sich ja von selbst.

    Zuerst immer schnellere Rechner und immer größeren Arbeitsspeicher entwickeln, nur um diese dann mit ineffizientem Code und durch Speicherverschwendung auszulasten, kann ja wohl nicht der Sinn der Übung sein.

    Außerdem war die ursprüngliche Frage hier ja gerade, wie man einen** CStringW möglichst effizient in einen std::string **"umwandeln" kann, insbesondere im Hinblick auf den Speicherverbrauch.

    EOP schrieb:

    EDIT:
    Wenn du gleich nach ASCII umwandelst und base64 decodierst sparst du eine Menge an Speicher.
    50% gegenüber UNICODE und ungefähr nochmal 4/5 nach dem base64 decodieren.

    EDIT #2:
    Quatsch, du sparst natürlich 1/5 und nicht 4/5.

    Deswegen hatte ich dem OP ja schon am Anfang empfohlen möglichst von vorne herein einen ANSI-String anstelle eines Wide-Strings für seine Base64-kodierten Daten zu verwenden, oder, falls möglich, die Base64-kodierten Daten direkt in Binär-Daten zu dekodieren.

    Es scheint aber so zu sein, dass er die Base64-Daten nun mal als** CStringW herein bekommt und als std::string **in eine Funktion weiter reichen muss...



  • DeathCubeK schrieb:

    Dass wir später nur das erste Byte aus dem Ausgabe-Array übernehmen sollte kein Problem sein, da praktisch alle Multibyte-Kodierungen zu ASCII abwärts-kompatibel sind, d.h. alle ASCII-Zeichen werden mit einem einzigen Byte und mit dem selben Bit-Mutser wie im ASCII-Standard dargestellt (Beispiel UTF-8: Jeder ASCII-kodierte Text ist automatisch auch ein 100% gültiger UTF-8 Text; In Umgekehrter Richtung gilt das natürlich nur, wenn der UTF-8 Text keine Zeichen enthält, die in ASCII nicht darstellbar sind).

    Ja, ich weiss dass fast alle MB-Codepages mit den 128 ASCII Codes anfangen. Gerade deswegen kann man das wctomb auch ganz weglassen, wenn garantiert ist dass man nur ASCII reinbekommt 😉



  • Danke für eure Antworten,

    ich habe jetzt von CStringW auf CStringA umgebaut.

    Wird hier nicht kopiert? Ich möchte nicht kopieren da ich dann soviel speicher brauche.

    std::string outputBuffer(cstrSource.GetBuffer());
    


  • lulu32_0 schrieb:

    Danke für eure Antworten,

    ich habe jetzt von CStringW auf CStringA umgebaut.

    Wird hier nicht kopiert? Ich möchte nicht kopieren da ich dann soviel speicher brauche.

    std::string outputBuffer(cstrSource.GetBuffer());
    

    Doch, hier wird kopiert (von CString nach std::string).
    cstrSource.ReleaseBuffer() nicht vergessen, gelle...



  • lulu32_0 schrieb:

    Danke für eure Antworten,

    ich habe jetzt von CStringW auf CStringA umgebaut.

    Wird hier nicht kopiert? Ich möchte nicht kopieren da ich dann soviel speicher brauche.

    std::string outputBuffer(cstrSource.GetBuffer());
    

    Es gibt, soweit ich weiß, keinen** std::string Konstruktor, dem man einen existierenden Puffer zur Weiterverwendung unterschieben kann (bei QByteArray **ginge das, siehe hier).

    ** std::string **allokiert stets seinen eignen internen Puffer und im Konstruktor wird der Eingabe-String in diesen Puffer kopiert:

    from c-string (4)
    ** string (const char* s); **
    Copies the null-terminated character sequence (C-string) pointed by s.

    from sequence (5)
    ** string (const char* s, size_t n); **
    Copies the first n characters from the array of characters pointed by s.

    Ich denke daher, es lässt sich nicht verhindern, dass die Daten zumindest 1x kopiert werden, wenn es ein** std::string **werden soll/muss.

    _________

    Indem Du zuvor bereits durchgängig mit** CStringA arbeitest, sparst Du Dir aber immerhin eine unnötige Kopie und der Overhead für die wchar_t nach char **Konverteirung entfällt.



  • Vielen Dank, ich glaube ich verstehe es jetzt.

    DeathCubeK schrieb:

    EOP schrieb:

    Irgendwie scheint es nur so zu funktionieren:

    CStringW hellocw ( _T("Hello World!") );
    CStringA helloca ( hellocw );
    string hellos ( helloca.GetString() );
    

    Nö, geht auch so:

    CStringW hellocw (L"Hello World!");
    const int len = hellocw.GetLength();
    std::string hellos;
    hellos.reserve(len);
    for(int i = 0; i < len; i++)
    {
    	hellos += (char) hellocw.GetAt(i);
    }
    printf("RESULT: \"%s\"\n", hellos.c_str());
    

    Immer vorausgesetzt, in "hellocw" kommen tatsächlich nur ASCII-Zeichen vor - sonst bekommst da Blödsinn raus 😉

    Hierzu aber noch eine Frage. Was meinst du mit nur ASCII - Zeichen? Gibt es in Unicode andere zeichen?



  • lulu32_o schrieb:

    Vielen Dank, ich glaube ich verstehe es jetzt.

    DeathCubeK schrieb:

    EOP schrieb:

    Irgendwie scheint es nur so zu funktionieren:

    CStringW hellocw ( _T("Hello World!") );
    CStringA helloca ( hellocw );
    string hellos ( helloca.GetString() );
    

    Nö, geht auch so:

    CStringW hellocw (L"Hello World!");
    const int len = hellocw.GetLength();
    std::string hellos;
    hellos.reserve(len);
    for(int i = 0; i < len; i++)
    {
    	hellos += (char) hellocw.GetAt(i);
    }
    printf("RESULT: \"%s\"\n", hellos.c_str());
    

    Immer vorausgesetzt, in "hellocw" kommen tatsächlich nur ASCII-Zeichen vor - sonst bekommst da Blödsinn raus 😉

    Hierzu aber noch eine Frage. Was meinst du mit nur ASCII - Zeichen? Gibt es in Unicode andere zeichen?

    ASCII war einer der ersten binären Kodierungen für Schriftzeichen überhaupt (noch aus der Fernschreiber-Ära). ASCII umfasst nur 128-Zeichen und ist eigentlich ein 7-Bit Code, wobei aber praktisch immer 8-Bit (d.h. ein char in C/C++) pro Zeichen gespeichert wird:
    http://www.asciitable.com/index/asciifull.gif

    Offensichtlich ist ASCII in seinem Umfang stark begrenzt und vor allem für die englische Sprache ausgelegt, weshalb später diverse Codepages für verschiedene Sprachen eingeführt wurden, z.B. Latin-1 (ISO 8859-1) oder Kyrillisch (ISO 8859-5). Diese Codepages ordnen die 256 möglichen Werte eines Bytes bzw. char's je nach Sprache unterschiedlich zu. Dabei sind die "unteren" 128 Zeichen in aller Regel mit ASCII identisch, die "oberen" 128 Zeichen hingegen werden Sprach-spezifisch zugeordnet.

    Der große Nachteil an Codepages ist, dass man je nach verwendeter Codepage immer nur Zeichen einer Sprache darstellen kann. Du könntest also nie lateinische, kyrillische und japanische Zeichen in einem Text mischen! Außerdem ergibt ein Text nur dann Sinn, wenn er mit der "korrekten" Codepage interpretiert wird. Die ist aber im Allgemeinen System-spezifisch. Eine Datei, die auf einem japanischen System erstellt wurde, würde auf Deinem deutschen System also nur Kauderwelsch ergeben.

    Unicode ist der Versuch einen Standard zu schaffen, der alle Schriftzeichen enthält, die auf der Welt vorkommen. Dabei definiert Unicode aber zunächst einmal nur eine (sehr großen) Zeichenvorrat, aber keine konkrete Kodierung! Unicode Zeichen können z.B. als UTF-16 oder als UTF-8 kodiert werden. Unter Windows enthält ein** wchar_t -String normalerweise Unicode-Daten in der UTF-16 Kodierung. Unter Linux hingegen, wird üblicherweise UTF-8 benutzt, so dass die Unicode-Daten in einen "normalen" char **-String passen.


Anmelden zum Antworten