Ich habe, wie vermutlich 99% aller Windows-Programmierer, das leidige Thema mit std::string und std::wstring :D
Nach diversen Nachforschungen bin ich auf den Trichter gekommen, dass es wohl am sinnvollsten ist, immer std::string zu verwenden und darin "einfach" UTF-8 zu speichern.
Bei API calls, die einen wstring oder wchar_t* brauchen, muss ich dann vor dem Aufruf konvertieren (und damit das alles auch funktioniert, beschränke ich mich bei der Kompatibilität mal auf C++11).
String s1(L"String aus wchar_t");
String s2("String aus char");
String neuerString = (String(L"wchar") + String("char")); //string operatoren funktionieren wie gewohnt
AufrufEinerEigenenFunktion(s1,s2);
AufrufEinerWinAPIFunktionMitWstr(s1());
AufrufEinerWinAPIFunktionDieLPCWSTRErwartet(st().c_str());
Vorteil ist, ich kann die Klasse überall ganz normal verwenden, inklusive aller bereits überladenen Operatoren.
Bei WINAPI calls nutze ich den operator(); den habe ich deshalb gewählt, weil er keine wichtige Funktionalität aus der std::string Klasse überdeckt und eine sehr hohe precedence hat, d.h. ich kann dann auch einfach wstring-methoden aufrufen, ohne mir einen abzubrechen oder mir einen Kopf darum zu machen (vgl. "s1().c_str()" mit "(*s1).c_str()", wenn ich operator* verwendet hätte, "*s1.c_str()" würde schiefgehen).
Da ich nur Standard-C++ verwende, denke ich sollte die Klasse auch unter Linux etc. laufen/compilieren, auch wenn sie dort nicht notwendig ist, weil ja alle API aufrufe den std::string als UTF-8 sehen.
Funktioniert für mich bisher sehr perfekt, ist ein Mini-Helfer der wunderbar alle meine Probleme löst. 8-)
Ich würde gerne wissen, ob ihr das für eine gute Idee haltet, oder ob ihr Probleme seht?
Eventuell habt ihr schonmal was ähnliches gebaut, oder habt Verbesserungsvorschläge?
Danke schonmal, Feedback aller Art würde mich freuen :)
Also ich kann jetzt nicht so viel dazu sagen, da ich es grundsätzlich vermeide irgendwas anderes als ASCII oder ISO-8859-1 zu verwenden (also auch kein wstring oder Widechar-Versionen bei WINAPI-Calls). Und für Tools für Endkunden, wo Sprachen wichtig werden, nutze ich heute C# wo das alles kein Problem ist.
Ich wollte aber was zu dem operator() sagen. Das gefällt mir ehrlich gesagt nicht so gut. Es ist absolut nicht ersichtlich welche Bedeutung dieser hat und führt eher zu Verwirrung. Besonders in komplexeren Ausdrücken. Hier würde ich lieber auf Nachvollziehbarkeit setzen und eine Methode ala to_wstring() spendieren.
Besten Dank schon mal.
Gute Idee, ich werde das wohl machen und eine to_wstring() Methode bauen, macht tatsächlich mehr Sinn und ist besser verständlich.
Ich finde es nur komisch dass das ganze Internet voller Fragen bzgl. UTF-8 und C++ ist.
Ich sehe oft, dass dann auf umfangreiche Libs und plattformspezifische Funktionen verwiesen wird (ICU, MultiBytetoWideChar, UTF8-CPP, UTF8 Strings library), dabei kann man das Problem anscheinend mit wenigen Zeilen Code komplett abfrühstücken.
Auch noch Multi-Platform fähig und reines Standard-C++.
Irgendwie bin ich davon ausgegangen, dass ich irgendwo einen Denkfehler gemacht habe :D
NytroX hat geschrieben:Ich sehe oft, dass dann auf umfangreiche Libs und plattformspezifische Funktionen verwiesen wird (ICU, MultiBytetoWideChar, UTF8-CPP, UTF8 Strings library), dabei kann man das Problem anscheinend mit wenigen Zeilen Code komplett abfrühstücken. Auch noch Multi-Platform fähig und reines Standard-C++. Irgendwie bin ich davon ausgegangen, dass ich irgendwo einen Denkfehler gemacht habe :D
Vor C++11 kannte der Standard kein UTF, folglich musste in der Vergangenheit viel Handarbeit reingesteckt werden.
NytroX hat geschrieben:Bei API calls, die einen wstring oder wchar_t* brauchen, muss ich dann vor dem Aufruf konvertieren (und damit das alles auch funktioniert, beschränke ich mich bei der Kompatibilität mal auf C++11). Folgende Klasse habe ich mir daher geschrieben:
Genau, bei manchen API-Calls - wie viele davon machst du? Willst du dir wirklich eine kaum nützliche, potentiell niedrig-qualitative Stringklasse als Abhängigkeit in deine gesamte Programmschnittstelle einhandeln, deren Funktionalität du dann an exakt 5 Stellen im gesamten Programm nutzt? Obendrein mit Header-only Conversion Code, der in jedes Modul die ganze Herrlichkeit von C++' Streaming-Bibliothek einbindet?
Ich empfehle simple Funktionen to_utf8 und to_wstring, im Optimalfall mit Definition in einer Übersetzungseinheit, die die ganzen codecvt-Abhängigkeiten versteckt. Dann kannst du in den seltenen Fällen, in denen du Konvertierung brauchst, noch immer sehr einfach darauf zugreifen, ohne dass du dir deine komplette Programmschnittstelle verschandelst.
Trotzdem noch ein Review für den interessierten Leser:
NytroX hat geschrieben:class String : public ::std::string
{
public:
String() = default;
// Achtung: MSVC generiert noch immer keinen Move-Ctor & -Assignment-Op.
// Im VS 2013 _November CTP_ kriegst du sie immerhin mit manuellem = default, davor musst du sie von Hand schreiben.
// In Zukunft wird das hier tatsächlich endlich unnötig sein,
// aber aus unerfindlichen Gründen hält MS diese elementarste Grundfunktionalität für niedrigprior.
String(String&& r) : string(std::move(r)) { }
String& operator =(String&& r) { this->string::operator =(std::move(r)); return *this; }
// Entweder pass-by-value ohne const und mit : string(std::move(string)) (eliminiert alle Kopien von temporären Strings)
// ODER pass-by-const ref mit : string(string) (eliminiert immerhin noch überflüssige Kopie von ALLEN Strings, aber erste Option optimaler)
String(const std::string string)
{
this->assign(std::move(string));
}
// Wie schon angesprochen, implizite Konvertierung ist für diese Operation überhaupt nicht sinnvoll.
// const ist bei der Rückgabe schon wieder eine ganz blöde Idee, killt erneut Move-Optimierung und macht bei _jeder_ Rückgabe eine unnötige Kopie. const std::wstring operator()to_wstring() const
{
std::wstring ws = std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().from_bytes(*this);
return ws;
}
};
CodingCat hat geschrieben: // const ist bei der Rückgabe schon wieder eine ganz blöde Idee, killt erneut Move-Optimierung und macht bei _jeder_ Rückgabe eine unnötige Kopie. const std::wstring operator()to_wstring() const
{
std::wstring ws = std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().from_bytes(*this);
return ws;
}
So wie ich das sehe ist da immernoch eine unnötige Kopie drin:
Denn wenn die Konstruktion von ws eliminiert worden wäre, würde das ja bedeuten, dass der Compiler RVO für wstring kann, und dann könnte man auch den Rückgabewert als wstring const belassen und auf RVO setzen statt Verschiebung zu bedenken. (Der Microsoft-STL-Mensch hat afaik auch in seinem „Don't help the compiler“-Vortrag erinnert, dafür keine temporären Variablen anzulegen.)
So wie ich das sehe geschieht das aber in der ursprünglichen Version doppelt: Erst wird das from_bytes()-Ergebnis konstruiert, dann in ws verschoben (1), dann zum Aufrufer von to_wstring() verschoben (2). Ohne ws kann das Ergebnis des from_bytes()-Ausdrucks direkt zum Aufrufer von to_wstring() verschoben werden statt erst nach ws, oder?
(Kommt darauf an, ob Visual C++ dem move Wirkung zuordnet und deshalb die Existenz von ws in jedem Fall erzwingt oder nicht. Da es sich um VC handelt, bin ich aber grundsätzlich pessimistisch. Ich bin pessimistisch, dass VC dem Destruktor von ws Wirkung zuschreibt weil es das if(!_begin) eines verschobenen Strings nicht statisch auflösen können wird.)
Ja, es wird mehrfach verschoben, aber nicht kopiert. Prinzipiell sollte alles Verschieben spätestens auf Registerebene wegoptimiert werden, allerdings hat sich VC++ da in der Tat in der Vergangenheit als hochgradig unzuverlässig erwiesen. Im Fall von std::string ist es insbesondere kein einfacher Null-Vergleich, sondern eine Small-String-Optimization-Fallunterscheidung.
Super, besten Dank für die vielen Details.
Korrigiert mich bitte wenn ich falsch liege, aber das "return" gibts doch effektiv so garnicht.
Da die Funktion im Header steht, wird sie ja ge-inlined (wenn der Compiler das denn machen mag :-)).
Hier mal die Funktion mit teilweiser Optimierung (VC2013):
Ob mit oder ohne Variable, der generierte Code sieht gleich aus.
Aufgerufen werden nur ctor, from_bytes und dtor; keine sinnlosen Kopien oder moves vorhanden, wenn ich nichts übersehen hab.
Btw. ist der scheinbar optimale code nicht immer der beste/schnellste; wenn ich weitere Optimierungen dazuschalte wird der Code wesentlich komplexer und scheint aus vielen sinnlosen Teilen zu bestehen :shock:
; und das hier soll angeblich schneller sein?
; naja, so genau kann man das ja aber nie sagen ohne profiling, sieht aber für mich sehr interessant aus, was der compiler da so bastelt...
;
; std::wstring to_wstring() const
; {
mov rax,rsp
push rdi
sub rsp,0A0h
mov qword ptr [rax-80h],0FFFFFFFFFFFFFFFEh
mov qword ptr [rax+8],rbx
mov qword ptr [rax+10h],rsi
mov rdi,rdx
mov rbx,rcx
xor esi,esi
mov dword ptr [rsp+20h],esi
; //return std::wstring_convert<::std::codecvt_utf8<wchar_t>, wchar_t>().from_bytes(*this);
; std::wstring ws = std::wstring_convert<::std::codecvt_utf8<wchar_t>, wchar_t>().from_bytes(*this);
lea rcx,[rax-78h]
call std::wstring_convert<std::codecvt_utf8<wchar_t,1114111,0>,wchar_t,std::allocator<wchar_t>,std::allocator<char> >::wstring_convert<std::codecvt_utf8<wchar_t,1114111,0>,wchar_t,std::allocator<wchar_t>,std::allocator<char> > (07FF6E4362DC0h)
nop
mov r8,rbx
mov rdx,rdi
mov rcx,rax
call std::wstring_convert<std::codecvt_utf8<wchar_t,1114111,0>,wchar_t,std::allocator<wchar_t>,std::allocator<char> >::from_bytes (07FF6E4362F00h)
nop
lea rax,[std::wstring_convert<std::codecvt_utf8<wchar_t,1114111,0>,wchar_t,std::allocator<wchar_t>,std::allocator<char> >::`vftable' (07FF6E43769B8h)]
mov qword ptr [rsp+30h],rax
cmp qword ptr [rsp+80h],8
jb os::String::to_wstring+62h (07FF6E4361872h)
mov rcx,qword ptr [rsp+68h]
call qword ptr [__imp_operator delete (07FF6E43763B0h)]
mov qword ptr [rsp+80h],7
mov qword ptr [rsp+78h],rsi
mov word ptr [rsp+68h],si
cmp qword ptr [rsp+60h],10h
jb os::String::to_wstring+8Bh (07FF6E436189Bh)
mov rcx,qword ptr [rsp+48h]
call qword ptr [__imp_operator delete (07FF6E43763B0h)]
mov qword ptr [rsp+60h],0Fh
mov qword ptr [rsp+58h],rsi
mov byte ptr [rsp+48h],0
mov rcx,qword ptr [rsp+40h]
test rcx,rcx
je os::String::to_wstring+0C1h (07FF6E43618D1h)
mov rax,qword ptr [rcx]
call qword ptr [rax+10h]
test rax,rax
je os::String::to_wstring+0C1h (07FF6E43618D1h)
mov r8,qword ptr [rax]
mov edx,1
mov rcx,rax
call qword ptr [r8]
; return ws;
mov rax,rdi
; }
lea r11,[rsp+0A0h]
mov rbx,qword ptr [r11+10h]
mov rsi,qword ptr [r11+18h]
mov rsp,r11
pop rdi
ret
@CodingCat: auch wenn viele der Punkte deines Reviews anscheinend das Compilat nicht essenziell beeinflussen, ist es doch gut zu wissen wie man es richtig macht. Habe ich leider noch nicht viel Erfahung mit, daher Danke für die Hinweise.
Wie auch immer, zurück zum Thema:
Genau, bei manchen API-Calls - wie viele davon machst du? Willst du dir wirklich eine kaum nützliche, potentiell niedrig-qualitative Stringklasse als Abhängigkeit in deine gesamte Programmschnittstelle einhandeln, deren Funktionalität du dann an exakt 5 Stellen im gesamten Programm nutzt?
Ich glaube da ist schon der richtige Hinweis; ist mir auch aufgefallen, als ich den operator() durch to_wstring() ersetzt habe, so oft kam das irgendwie garnicht vor im code... ;)
Ich benutze für die API-Calls ja auch eine Abstraktionsschicht; muss ja sein. Selbst wenn einem andere Betriebssysteme völlig egal sind, wird man die Aufrufe irgendwie sinnvoll kapseln, die WinAPI wäre ja sonst Horror in der Benutzung.
Und dann ist natürlich die Frage, in wie weit ich die Klasse dann überhaupt verwende/verwenden sollte, denn sie zieht sich ja tatsächlich entweder durch das komplette Programm, oder ich verwende sie nur innerhalb der Abstraktionsschicht; und dann reichen die Funktionen ja auch aus (auch wenn es theoretisch nicht die feine OOP-Art ist; ist die WinAPI aber sowieso nicht).
Und spätestens mit Internationalisierung und/oder in einem echten Programm nutze ich ja Strings hauptsächlich aus Dateien, d.h. ein String(L"TollerString"); kommt ja eher selten bis gar nicht in einer reellen Anwendung vor.
Danke nochmal für eure Zeit, um so ein simples Problem zu diskutieren, war super hilfreich.
Ich werde wohl zukünftig auf die Funktionshelfer setzen, und sie im namespace der Abstraktionsschicht für die API-Calls usw. einpacken.
Vor C++11 kannte der Standard kein UTF, folglich musste in der Vergangenheit viel Handarbeit reingesteckt werden.
Genau, und jetzt ist die Geschichte ein für alle mal sinnvoll gelöst und damit vom Tisch; besten Dank nochmal an euch beide :-)
NytroX hat geschrieben:Korrigiert mich bitte wenn ich falsch liege, aber das "return" gibts doch effektiv so garnicht.
Da die Funktion im Header steht, wird sie ja ge-inlined (wenn der Compiler das denn machen mag :-)).
Bei dieser Funktion ist Inlining unwahrscheinlich, da passiert intern genug was du nicht wissen willst und ganz sicher nicht hundertfach in deine Exe kopiert haben möchtest, siehe deine eigenen ASM-Listings.
NytroX hat geschrieben:Hier mal die Funktion mit teilweiser Optimierung (VC2013): [...] Ob mit oder ohne Variable, der generierte Code sieht gleich aus.
Wie Krishty schon spekulierte, wird hier erfolgreich Return Value Optimization betrieben, d.h. auf Funktionsseite wird der Ergebnis-String tatsächlich direkt in den Rückgabewert konstruiert, ohne Move oder Kopie. Aber trotzdem Vorsicht mit dem const, um nochmal darauf zurückzukommen:
Krishty hat geschrieben:Denn wenn die Konstruktion von ws eliminiert worden wäre, würde das ja bedeuten, dass der Compiler RVO für wstring kann, und dann könnte man auch den Rückgabewert als wstring const belassen und auf RVO setzen statt Verschiebung zu bedenken.
Fast, denn RVO greift auf Aufruferseite nur, wenn das Ergebnis des Aufrufs einem neuen Objekt gleichen Typs zugewiesen wird. Steht auf Aufruferseite hingegen eine Zuweisung zu einem bestehenden Objekt, Objekt anderen Typs oder ein sonstiger R-Value-optimierbarer Ausdruck, ist RVO aus Prinzip nicht anwendbar und das const blockiert wieder eine einfache Verschiebung, wodurch eine ggf. teure Kopie notwendig wird. Fazit: RVO deckt immer nur einen Teil der möglichen Aufruffälle ab. Obwohl man sich im genannten Fall bei modernen Compilern auf RVO gut verlassen kann, bleibt es also dennoch eine schlechte Idee, verschiebbare Objekte const zurückgegeben.
NytroX hat geschrieben:Btw. ist der scheinbar optimale code nicht immer der beste/schnellste; wenn ich weitere Optimierungen dazuschalte wird der Code wesentlich komplexer und scheint aus vielen sinnlosen Teilen zu bestehen :shock:
Nein, was du siehst wird in beiden Fällen ausgeführt, in zweiterem wurden jedoch Teile in deine Hilfsfunktion geinlinet. Hier siehst du das volle Grauen der STL IO Stream-Bibliothek, selbst die UTF-Konvertierung geht durch V-Tables und mehrere Allokationen.
NytroX hat geschrieben:Genau, und jetzt ist die Geschichte ein für alle mal sinnvoll gelöst und damit vom Tisch; besten Dank nochmal an euch beide :-)
Jetzt weißt du, dass sinnvoll immer relativ ist. ;)
Krishty hat geschrieben:Denn wenn die Konstruktion von ws eliminiert worden wäre, würde das ja bedeuten, dass der Compiler RVO für wstring kann, und dann könnte man auch den Rückgabewert als wstring const belassen und auf RVO setzen statt Verschiebung zu bedenken.
Fast, denn RVO greift auf Aufruferseite nur, wenn das Ergebnis des Aufrufs einem neuen Objekt gleichen Typs zugewiesen wird. Steht auf Aufruferseite hingegen eine Zuweisung zu einem bestehenden Objekt, Objekt anderen Typs oder ein sonstiger R-Value-optimierbarer Ausdruck, ist RVO aus Prinzip nicht anwendbar und das const blockiert wieder eine einfache Verschiebung, wodurch eine ggf. teure Kopie notwendig wird. Fazit: RVO deckt immer nur einen Teil der möglichen Aufruffälle ab. Obwohl man sich im genannten Fall bei modernen Compilern auf RVO gut verlassen kann, bleibt es also dennoch eine schlechte Idee, verschiebbare Objekte const zurückgegeben.
Das ist völlig richtig. Damit hast du mir auch erklärt, warum ich unbewusst fast nur noch Single Assignment Form programmiere :D
@Krishty und Coding Cat: Ihr seid ja hier im Forum bekanntermaßen die "Cracks" wenn es um Optimierung und Analyse von Code geht. Für mich ist es oft schwer da zu folgen. Hättet ihr nicht mal Lust einen Artikel oder Forenbeitrag zu schreiben, wo ihr ein paar Tipps gebt, was man tun sollte und was nicht. Oder auch so gängige Kniffe, die man öfters mal brauchen kann. Mich würde auch mal interessieren, wie stark sich solche DInge letztlich auf die Gesamtperformance, Compilezeit, usw auswirken. Natürlich ist das immer abhängig von Codeumfang und anderen Faktoren, aber so grobe Hausnummern wären mal interessant. Ich weiß immer nicht ob sich z.B. der Aufwand lohnt irgendwo eine Kopie zu vermeiden. Wie gesagt ist es für mich oft schwer wirklich zu erkennen, was der Compiler da genau macht. Wäre echt toll wenn ihr mal ein bisschen Zeit findet für sowas. Ich glaub ich bin nicht der einzige, den das interessiert. ;)
Ich kenne zwar den Mikrooptimierungs-Log von Krishty, den ich übrigens sehr gut finde, aber ich meine eher so in Richtung "Hintergründe erklären". Natürlich gern auch anhand von Beispielen.
Also wenn ihr Lust habt mich/uns an eurem Wissen teilhaben zu lassen, würde ich mich freuen. ;)
Hier findest du den Vortrag Don't Help the Compiler von Stephan T. Lavavej, der Microsoft die STL wartet und erklärt, was man mit vector und string machen soll und was nicht. Wenn er das erklärt ist das sicherlich verständlicher als meine Mutmaßungen.
Auf die Laufzeitleistung wirkt sich sowas quasi garnicht aus. Aber immerhin spart man eine Zeile und eine Variable, wenn man ws weglässt.
Was die Kompilierzeit angeht: nur-Header-Bibliotheken wie die STL haben die unangenehme Eigenschaft, alle Abhängigkeiten reinzuziehen. Abgesehen davon, dass das furchtbarer Programmentwurf ist, umfassen die Basisabhängigkeiten der STL bei Visual Studio 2012 gegen 100.000 Zeilen und bewirken damit rund eine Sekunde Übersetzungszeit pro .cpp auf meinem PC, der aber zugegebenerweise schon älter ist.
Eine weitere unangenehme Eigenschaft von nur-Header-Bibliotheken ist, dass jede Übersetzungseinheit eine Kopie jeder Funktion der Bibliothek bekommt, und dass der Linker alle Duplikate wieder zusammenfassen muss. Eine .lib auf meiner Arbeit habe ich von 530 MiB auf 270 gedrückt indem ich alle inline-Definitionen aus unseren eigenen Headern in .cpps verschoben habe. Dementsprechend ist auch der Arbeitssatz von Compiler und Linker gesunken und die Kompilierzeit hat sich ein Bisschen verkürzt.
Ich unterstütze darum Cats Vorschlag, zwei globale Funktionen zu deklarieren und in einem eigenen Modul zu definieren, voll und ganz. Überhaupt unterstütze ich das Modulsystem von C, das dem von C++ in Kapselung und Leistung absolut überlegen ist.
Vielen Dank für den Link. Das werd ich mir gleich angucken wenn ich zu Hause bin. Und auch danke für die Tipps zum Thema Header/Modul. Das Problem bei der STL ist ja vermutlich das "T" im Namen.
Ja wegen der STL würde es Sinn machen die precompiled header zu benutzen... wenn das nur nicht so verbuggt wäre im MSVC.
Ist genauso beim inkrementellen/teilweisen Build, das führt bei mir fast immer zu undefiniertem Verhalten.
Der baut da irgendwie immer Müll zusammen, es gibt keine sinnvolle Regelung wann er was neu erstellt und wann nicht.
Erst wenn ich dann eine komplette Neuerstellung (clean+build) mache geht wieder alles; diese Funktionen sind total sinnfrei, ich baue fast immer das Projekt komplett neu (und mache dann lieber mehrere Projekte in einer Mappe).
Und dann fallen die Zeiten halt auf; sobald man vector, map und string drin hat wird es schon merklich langsamer (und das sind so einfache Dinge die braucht man ja in fast jedem Projekt).
Überrascht mich; ich habe bis jetzt ja fast alles kaputt gekriegt; aber trotz bisweilen exzessiver Template-Experimente konnte ich mich über inkrementelle Builds und Precompiled Headers eigentlich nie beklagen.
Auf der Arbeit haben wir uns darauf geeinigt, dass beim Hinzufügen / Entfernen von Attributen erstmal komplett neu kompiliert wird weil vorkompilierte Header da in gut einem Viertel der Fälle versagen und die Hälfte des Programms mit dem alten Datentyp weiterarbeitet. Ich konnte das nie isolieren, aber das Problem besteht bei großen Projekten mit VS 2010 und 2012 definitiv. Katzen haben einfach neun Leben :)
Seit VC++6.0 habe ich mich von PCH verabschiedet. Es war damals ein Krampf (so wie vieles bei VC++6.0). Gestern habe ich aber mal wieder einen Versuch gewagt, da mein Projekt schon recht riesig ist und ich bei kleinen Änderungen teilweise alles neu bauen muss. Sofern man nur STL-Header im PCH einbezieht, sollte das ja recht problemlos klappen, da sich die nicht ändern. Das bringt bei mir schon etliche Sekunden Compile-Zeit, weil ich in fast jeder meiner rund 100 Dateien auf STL-Container oder -Strings zurückgreife. Zusätzlich habe ich dann nur noch ein paar Header drin, die häufig verwendete Konstanten enthalten, die sich aber sehr selten mal ändern. Scheint ganz gut zu funktionieren. Ich könnte natürlich auch noch Header mit so Sachen wie Vektoren, Matritzen, usw. in den PCH packen, aber dann handle ich mir eventuell wieder die von Krishty genannten Probleme ein. Eine sichere Teil-Optimierung ist hier aber immerhin schonmal besser als gar keine. ;)
Ich mache das wie folgt, habs nur fix kopiert und daher nicht aufgeräumt. Die ganzen "gf_Funktionen" begleiten mich schon länger.
Besser ist es sicher nicht, aber hat sich bewährt.
odenter hat geschrieben:Ich mache das wie folgt, habs nur fix kopiert und daher nicht aufgeräumt. Die ganzen "gf_Funktionen" begleiten mich schon länger.
Besser ist es sicher nicht, aber hat sich bewährt.
std::wstring für Multibyte Strings (Unicode) ansonsten std::string.
std::to_string kannte ich nicht scheint es ja auch für wstring's zu geben, gleich mal abändern.
EDIT:
Der Unterschied, abgeshen von OOP vs. kein OOP, ist das ich einen MultibyteString speichere und Du einen MultibyteString in einen SinglebyteString kovertierst und zurück.