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
nachstd::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 nenstd::string
'draus machen? Die speicherfreundlichste Variante wäre nämlich die Base64 Dekodierung gleich mit demCStringW
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. SowohlCStringW
als auchCStringA
sind lediglich Spezialisierungen vonCStringT
. UndCString
(ohne A oder W) ist ein Alias für entwederCStringW
oderCStringA
, 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 schreibeCString
(meistens die richtige Wahl). Wenn Du aber explizit einen Wide- bzw. ANSI-String haben willst, schreibeCStringW
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 aberCStringW
durchCStringA
**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 Deinenstd::string
als Parameter übergeben. Das nur, weil Du weiter oben meintest, Du müsstest deinenstd::string
noch einmal durch Umkopieren in einenconst 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 sindWenn 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 denCStringW
"cstrSource" Zeichen für Zeichen via+=
Operator an Deinenstd::string
**an.Die einzelnen Zeichen, die Du Dir von Deinem**
CStringW
perGetAt()
abholst, werden dabei einfach vonwchar_t
aufchar
"abgeschnitten", da Du auf diewctomb()
**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 einenCStringA
zu kopieren, nur um ihn dann ein weiteres mal in einenstd::string
**zu kopieren, erscheint mir aber irgendwie wenig sinnvollAußerdem kann der Konstruktor von**
CStringA
, der einenCStringW
als Argument annimmt, intern letztendlich auch nichts anderes tun, als den Eingabe-String Zeichen für Zeichen vonwchar_t
nachchar
**zu konvertieren.Da kann man diese Konvertierung auch direkt von**
CStringW
nachstd::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 einenstd::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 alsstd::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 (beiQByteArray
**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 diewchar_t
nachchar
**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.gifOffensichtlich 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.