Meine UTF-8 String Klasse

Design Patterns, Erklärungen zu Algorithmen, Optimierung, Softwarearchitektur
Forumsregeln
Wenn das Problem mit einer Programmiersprache direkt zusammenhängt, bitte HIER posten.
Antworten
NytroX
Establishment
Beiträge: 363
Registriert: 03.10.2003, 12:47

Meine UTF-8 String Klasse

Beitrag von NytroX »

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).

Folgende Klasse habe ich mir daher geschrieben:

Code: Alles auswählen

class String : public ::std::string
{
public:
	String() = default;

	String(const std::string string)
	{
		this->assign(string);
	}

	String(const char* string)
	{
		this->assign(string);
	}

	String(const std::wstring string)
	{
		this->assign(std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().to_bytes(string));
	}

	String(const wchar_t* string)
	{
		this->assign(std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().to_bytes(string));
	}

	const std::wstring operator()() const
	{
		std::wstring ws = std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().from_bytes(*this);
		return ws;
	}
};
Die Verwendung funktioniert wie folgt:

Code: Alles auswählen

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 :)
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: Meine UTF-8 String Klasse

Beitrag von BeRsErKeR »

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.
Ohne Input kein Output.
NytroX
Establishment
Beiträge: 363
Registriert: 03.10.2003, 12:47

Re: Meine UTF-8 String Klasse

Beitrag von NytroX »

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
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Meine UTF-8 String Klasse

Beitrag von CodingCat »

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));
}

String(const char* string)
{
this->assign(string);
}

// Überflüssige Kopie, pass-by-const ref
String(const std::wstring& string)
{
this->assign(std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().to_bytes(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;
}
};
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Krishty
Establishment
Beiträge: 8238
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Meine UTF-8 String Klasse

Beitrag von Krishty »

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:

  return std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().from_bytes(*this);

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.)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Meine UTF-8 String Klasse

Beitrag von CodingCat »

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 [...]
Nein, wenn der Rückgabeausdruck eine lokale Variable benennt, deren Typ mit dem Rückgabetyp übereinstimmt, ist Move garantiert.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Krishty
Establishment
Beiträge: 8238
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Meine UTF-8 String Klasse

Beitrag von Krishty »

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.)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Meine UTF-8 String Klasse

Beitrag von CodingCat »

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.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
NytroX
Establishment
Beiträge: 363
Registriert: 03.10.2003, 12:47

Re: Meine UTF-8 String Klasse

Beitrag von NytroX »

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):

Code: Alles auswählen

;		const 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         rdi,rdx  
mov         rbx,rcx  
and         dword ptr [rsp+20h],0  
;			//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> > (07FF734242944h)  
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 (07FF734242B24h)  
nop  
lea         rcx,[rsp+30h]  
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> > (07FF734242A9Ch)  
;			return ws;
mov         rax,rdi  
;		}
mov         rbx,qword ptr [this]  
add         rsp,0A0h  
pop         rdi  
ret  
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:

Code: Alles auswählen

; 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 :-)
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Meine UTF-8 String Klasse

Beitrag von CodingCat »

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. ;)
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Krishty
Establishment
Beiträge: 8238
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Meine UTF-8 String Klasse

Beitrag von Krishty »

CodingCat hat geschrieben:
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
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: Meine UTF-8 String Klasse

Beitrag von BeRsErKeR »

[Offtopic]

@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. ;)

[/Offtopic]
Ohne Input kein Output.
Benutzeravatar
Krishty
Establishment
Beiträge: 8238
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Meine UTF-8 String Klasse

Beitrag von Krishty »

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: Meine UTF-8 String Klasse

Beitrag von BeRsErKeR »

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.
Ohne Input kein Output.
NytroX
Establishment
Beiträge: 363
Registriert: 03.10.2003, 12:47

Re: Meine UTF-8 String Klasse

Beitrag von NytroX »

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).
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Meine UTF-8 String Klasse

Beitrag von CodingCat »

Ü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.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Krishty
Establishment
Beiträge: 8238
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Meine UTF-8 String Klasse

Beitrag von Krishty »

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 :)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: Meine UTF-8 String Klasse

Beitrag von BeRsErKeR »

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. ;)
Ohne Input kein Output.
odenter
Establishment
Beiträge: 207
Registriert: 26.02.2009, 11:58

Re: Meine UTF-8 String Klasse

Beitrag von odenter »

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.

Code: Alles auswählen

#ifndef _GLOBAL_H
#define _GLOBAL_H

#include <Windows.h>
#include <sstream>
#include <iostream>
#include <string>
#include <sigc++\sigc++.h>
#include <boost\shared_ptr.hpp>
#include <boost/lexical_cast.hpp>
#include <list>
#include <vector>
#include "Vector3d.h"
#include <irrlicht.h>
#include <CEGUI.h>
#include <RendererModules\Irrlicht\CEGUIIrrlichtRenderer.h>


#ifdef _DEBUG
#pragma comment(lib, ".\\_extern\\libsigc++-2.2.0\\MSVC_Net2003\\Debug\\sigc-2.0.lib")
#else
#pragma comment(lib, ".\\_extern\\libsigc++-2.2.0\\MSVC_Net2003\\Release\\sigc-2.0.lib")
#endif

#pragma comment(lib, "Irrlicht.lib")

#ifdef _DEBUG
#pragma comment(lib, "CEGUIBase_d.lib")
#pragma comment(lib, "CEGUIIrrlichtRenderer_d.lib")
#else
#pragma comment(lib, "CEGUIBase.lib")
#pragma comment(lib, "CEGUIIrrlichtRenderer.lib")
#endif

//typedef std::wstring WideString;

#ifdef _UNICODE
	typedef std::wstring String;
	typedef std::wstringstream StringStream;	
#else
	typedef std::string String;
	typedef std::ofstringstream StringStream;
#endif


typedef irr::s16 INT16;
typedef irr::s32 INT32;
typedef irr::s64 INT64;
typedef irr::f64 DOUBLE;
typedef irr::f32 FLOAT;
typedef irr::u8 UINT8;
typedef irr::u16 UINT16;
typedef irr::u32 UINT32;
typedef irr::u64 UINT64;


void gf_ODS(String msg);

String gf_ToString(DOUBLE val);
String gf_ToString(FLOAT val);
String gf_ToString(INT8 val);
String gf_ToString(INT16 val);
String gf_ToString(INT32 val);
String gf_ToString(INT64 val);
String gf_ToString(UINT32 val);
#ifdef _UNICODE
String gf_ToString(const wchar_t* val);
String gf_ToString(const char* val);
#else
String gf_ToString(const wchar_t* val);
String gf_ToString(const char* val);
#endif
String gf_ToString(String& val);

UINT8 gf_ToUINT8(String val);
INT8 gf_ToInt8(String val);
INT16 gf_ToInt16(String val);
INT32 gf_ToInt32(String val);
FLOAT gf_ToFloat(String val);
DOUBLE gf_ToDouble(String val);

std::string StringToString(const std::wstring& value);
std::wstring StringToString(const std::string& value);

#ifdef _UNICODE
#define _WTS(stringValue) StringToString(stringValue)
#define _STW(stringValue) StringToString(stringValue)
#else
#endif

// forward declaration
class Vector3d;
DOUBLE GetVectorDistance(Vector3d left, Vector3d right);

#endif

Code: Alles auswählen

#include "global.h"

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
void gf_ODS(String msg)
{
#ifdef _DEBUG
	OutputDebugString(msg.c_str());
#endif
}

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
String gf_ToString(DOUBLE val)
{
	StringStream oss;
	oss << val;
	return oss.str();
}

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
String gf_ToString(FLOAT val)
{
	StringStream oss;
	oss << val;
	return oss.str();
}

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
String gf_ToString(INT8 val)
{
	StringStream oss;
	oss << val;
	return oss.str();
}

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
String gf_ToString(INT16 val)
{
	StringStream oss;
	oss << val;
	return oss.str();
}

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
String gf_ToString(INT32 val)
{
	StringStream oss;
	oss << val;
	return oss.str();
}

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
String gf_ToString(INT64 val)
{
	StringStream oss;
	oss << val;
	return oss.str();
}

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
String gf_ToString(UINT32 val)
{
	StringStream oss;
	oss << val;
	return oss.str();
}

#ifdef _UNICODE
/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
String gf_ToString(const wchar_t* val)
{
  return std::wstring(val);
}

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
String gf_ToString(const char* val)
{
  return _STW(std::string(val));
}
#else
/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
String gf_ToString(const wchar_t* val)
{
  return _WTS(std::sstring(val));
}

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
String gf_ToString(const char* val)
{
  return std::string(val);
}
#endif

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
UINT8 gf_ToUINT8(String val)
{
	UINT8 ret = boost::lexical_cast<UINT8>(_WTS(val.c_str()));
	return ret;
}

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
FLOAT gf_ToFloat(String val)
{
	FLOAT ret = boost::lexical_cast<FLOAT>(_WTS(val.c_str()));
	return ret;
}

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
DOUBLE gf_ToDouble(String val)
{
	DOUBLE ret = boost::lexical_cast<DOUBLE>(_WTS(val.c_str()));
	return ret;
}

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
INT8 gf_ToInt8(String val)
{
	INT8 ret = boost::lexical_cast<INT8>(_WTS(val.c_str()));
	return ret;
}

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
INT16 gf_ToInt16(String val)
{
	INT16 ret = boost::lexical_cast<INT16>(_WTS(val.c_str()));
	return ret;
}

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
INT32 gf_ToInt32(String val)
{
	INT32 ret = boost::lexical_cast<INT32>(_WTS(val.c_str()));
	return ret;
}
 
/**********************************************************************************************************************
 * dummy funktion
 **********************************************************************************************************************/
String gf_ToString(String& val)
{
  return val;
}

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
std::string StringToString(const std::wstring& value)
{
	return std::string().assign(value.begin(), value.end());
}

/**********************************************************************************************************************
 *
 **********************************************************************************************************************/
std::wstring StringToString(const std::string& value)
{
	return std::wstring().assign(value.begin(), value.end());
}

DOUBLE GetVectorDistance(Vector3d left, Vector3d right)
{
  Vector3d result = left - right;

  DOUBLE distance = sqrt(pow(result._x, 2) + pow(result._y, 2) + pow(result._z, 2));
  return distance;
}
Benutzeravatar
Biolunar
Establishment
Beiträge: 154
Registriert: 27.06.2005, 17:42
Alter Benutzername: dLoB

Re: Meine UTF-8 String Klasse

Beitrag von Biolunar »

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.
Nicht sicher was der Code mit UTF-8 oder Unicode zu tun hat :-?

PS: std::to_string im Header <string> macht fast alle der gf_ToString funktionen überflüssig
odenter
Establishment
Beiträge: 207
Registriert: 26.02.2009, 11:58

Re: Meine UTF-8 String Klasse

Beitrag von odenter »

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.
Antworten