wstring vs string - kurze Nachfrage



  • Was bietet wstring nicht an?



  • UTF-16 wie auf den vorherigen Seiten diskutiert.



  • QString gibt dir auf Anfrage einen wstring in UTF-16 oder UCS-4, je nach Plattform. In beiden Fällen wird unter den o.g. Voraussetzungen jedes Zeichen in einem wchar_t abgebildet.

    • QString::toStdWString()
      Wenn Du willst kannst Du auch ein Array von unsigned short bekommen.
    • QString::utf16()


  • Eisflamme schrieb:

    hättest du zum ersten mal nicht-trivialen Gebrauch einer Doku gemacht.

    Na ja, wenn Du mir einfach Dinge unterstellst, kann ich Deine Kritik an meiner Kritik einer Antwort (was hier im Forum aber sowieso - sinnloserweise - kategorisch ein No-Go ist) nicht ernst nehmen, auch wenn da wahre Punkte dran sind.

    Denk mal drüber nach was du da schreibst geschrieben hast. Macht nämlich keinen Sinn.

    Und lies dir nochmal diese Aussage von dir durch:

    Genau, ich lese die gesamte QT Dokumentation durch und folge allen "siehe auch"-Links.

    Niemand hat von dir verlangt allen "siehe auch"-Links zu folgen.



  • Niemand hat von dir verlangt allen "siehe auch"-Links zu folgen.

    Damit wollte ich sagen: Präzisier den Hinweis bitte, sonst kannst Du ihn Dir auch sparen. "Lies die Doku" ist ein so allgemeiner Ratschlag, dass man ihn sich einfach sparen kann. Und ja, natürlich hätte ich toStdString() noch genauer durchlesen können, wenn ich mich besonders für QT interessiert hätte - im Fokus steht aber (siehe Threadtitel) die Variante ohne QT, dafür mit Standard.

    Macht nämlich keinen Sinn.

    Du unterstellst mir indirekt, ich hätte vorher noch nie nicht-trivialen Gebrauch von einer Doku gemacht. Oder bestreitest Du das?



  • Hallo Eisflamme und alle interessierten,

    ich denke man muss hier zwischen zwei Dingen ganz klar unterscheiden.

    Da ist zu einem mal das Coding von Zeichen, d.h. die Festlegung welche Zahl (oder Wert oder Code oder Codepoint) für welches Zeichen steht. Zum Beispiel die Zahl 65 (Hex: 0x41) steht für das große 'A' - das ist sowohl ASCII als auch Unicode. Diese Festlegung (65 := 'A') ist zunächst mal ziemlich unabhängig davon in welchem C++-Typ dieses Zeichen untergebracht wird und wie viel Speicherplatz er benötigt. Im Grunde legt der Unicode fest, was überhaupt ein Zeichen ist - das kann in einigen Fällen sehr knifflig werden (z.B.: toupper("Maße")); aber das ist hier gar nicht das Thema.

    Wenn da steht:

    std::string hello = "Hallo Welt!";
    

    so wird das im Allgemeinen immer (auch) Unicode sein. Alle von mir hier verwendeten Zeichen sind Unicode-Zeichen mit Werten <=0x7f. Somit kann sowohl ein std::string und erst recht ein std::wstring mit sizeof(wchar_t)==2 Texte mit Unicode-Zeichen enthalten - ohne jedes Encoding.

    Zum anderen geht es darum, auf welche Weise viele Zeichen (letztlich Texte, Dateien, Emails, usw. ) in Medien transportiert werden, wo die Byte-Größe beschränkt ist. Ich sage ganz bewusst 'Byte' und nicht 'Zeichen'. Das Euro-Zeichen hat den Unicode 8364 (Hex: 20AC) und das passt nun mal nicht in ein Byte von 8Bit - also braucht man mehr als eines. UTF-8 wäre eine Lösung; hier wird das Unicode-'€' als eine Folge von drei Byte dargestellt (0xE2 0x82 0xAC).
    Mag sein, das UTF-8&Co im Kontext und für Unicode entwickelt wurde. Aber es handelt sich um nichts anderes als eine Vorschrift eine Zahl im Bereich von 0 bis 0xFFFFFFFF in eine Bytefolge zu verpacken. Was diese Zahl dann bedeutet oder nicht ist davon zunächst mal völlig unabhängig.

    Ein std::basic_string<> - egal ob mit char oder wchar_t als Element-Typ - ist dafür gemacht, Zeichen zu enthalten, und keine Byte-Kopie einer Datei oder den Auswurf einer Socket-Schnittstelle.

    Nun ist es aber so, dass UTf-8/16 so designend ist, dass eine Folge von char, bei denen jedes <=0x7f ist, gleichzeitig Unicode und gültiges UTF-8 sei kann. Das gleiche gilt für eine String aus wchar_t mit sizeof(wchar_t)==2 mit Zeichen deren Code <=0x7fff; es kann gleichzeitig Unicode und gültiges UTF-16 sein kann. Das ist zum einen sehr praktisch, da die Information ob und welches Encoding vorliegt, i.a. außerhalb der Bytemenge (nicht!?) definiert ist (siehe aber BOM) und liest man es falsch, kommt trotzdem oft lesbarer Text heraus - ein paar Umlaute, die falsch dargestellt werden, stören nicht wirklich.
    Zum anderen wird beides aber auch oft verwechselt und in einen Topf geworfen.

    Das ist meines Erachtens auch der Grund warum QString::toStdWString folgendes tut: "The std::wstring is encoded in utf16 on platforms where wchar_t is 2 bytes wide" - in der Praxis ist das (meistens!) eh' das gleiche wie reiner Unicode da selbst chinesische Zeichen zum großen Teil im Bereich <=0x7fff liegen. Auf der anderen Seite geht aber keine Information verloren, wenn man wirklich Wert auf das 'letzte' Zeichen legt. Soll halt der Anwender damit klar kommen.

    Der Artikel http://www.utf8everywhere.org/ und insbesondere der Vorschlag http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3572.html unterscheiden nur wenig oder gar nicht.
    Was das Proposal ignoriert ist die Tatsache, dass der C++-Standard schon sehr genau zwischen externen Bytes (oder Words) und internen Zeichen unterscheidet. C++ tut dies in der Facette std::codecvt und in C++11 sind konkrete Derivate definiert, die die ganze UTF-Familie abdecken (z.B.: http://www.cplusplus.com/reference/codecvt/codecvt_utf8).
    Irritierender Weise erwähnt das Proposal das mit keinem Wort.

    Um auf das Problem von Eisflamme zurück zu kommen. Gäbe es die Möglichkeit, gänzlich auf String-Typen zu verzichten? Fange die Zeichen (meinetwegen als Typ wchar_t) da ab wo sie als Input anstehen und parse an einer std::basic_streambuf-Schnittstelle.
    Was wird denn eigentlich eingelesen? Also von welchem Typ sind die Objekte nach(!) den Parsen - sind dann noch Texte im Spiel?
    Gib' doch mal ein Beispiel.

    Gruß
    Werner



  • Hi,

    danke für diese sehr konstruktive und zum Thema gehörige Antwort. 🙂

    Also der Input kommt aus der Zwischenablage, die ich über QT verwende. Somit liegt der Input als QString vor. Wie QString aufgebaut ist, weiß ich nicht, daher weiß ich auch nicht, ob dort wirkliche Zeichen enthalten sind oder nur die Repräsentation in Bytes.

    Reales Beispiel würde wieder >200 Zeilen Erklärung benötigen. Nach dem Parsen steht eine listenartige Struktur aus, die hauptsächlich Zahlen und enums enthält. Es gibt dann noch ein paar Strings, aber deren Zeichen sind alle vom int-Wert <128, also unproblematisch. Erwähnenswerte könnte noch sein, dass Teile des Textes mit Gleichheit auf konstante Strings überprüft werden. Man muss also nach bestimmten konstanten Wörtern im Text suchen können oder diese auch ersetzen.

    Hilft das an Details weiter? Ich kann das gerade schlecht einschätzen, weil ich noch nicht weiß, worauf Du hinauswillst. 🙂

    Beste Grüße und danke!



  • an der Altlast wstring wird die Realität noch lange zu knappern haben
    Insbesondere gilt dies für Projekte welche in der Steinzeit mal meinten das sie alles oder primär genau so machen müssen wie Platformen die wstring (16bit) bevorzugt zur Textdarstellung verwenden.

    Qt, zum Beispiel und weil es im original Post erwähnt wurde, beginnt sich jedoch schon auf die Realität einzustellen.

    http://qt-project.org/forums/viewthread/17617

    http://www.macieira.org/blog/2012/05/source-code-must-be-utf-8-and-qstring-wants-it/



  • Eisflamme schrieb:

    Somit liegt der Input als QString vor. Wie QString aufgebaut ist, weiß ich nicht, daher weiß ich auch nicht, ob dort wirkliche Zeichen enthalten sind oder nur die Repräsentation in Bytes.

    RTFM

    Detailed Description

    The QString class provides a Unicode character string.

    QString stores a string of 16-bit QChars, where each QChar corresponds one Unicode 4.0 character. (Unicode characters with code values above 65535 are stored using surrogate pairs, i.e., two consecutive QChars.)



  • Jaa, es ist doch auch einfach nicht relevant, sonst hätte ich es auch nachgeschlagen. Ich sag am besten gar nichts mehr...



  • Hallo Eisflamme,

    nicht verzweifeln! 😉

    Du hast also Text aus der Zwischenablage, den Du Dir wahrscheinlich mit

    QClipboard *clipboard = QApplication::clipboard();
        QString text = clipboard->text();
    

    abgeholt hast. Jetzt möchtest Du daraus eine nicht triviale Struktur lesen und alles in C++. Das ist in C++ die natürliche Aufgabe eines Streams. Also brauchst Du zunächst mal einen Streambuf, denn ohne Streambuf läuft der Stream leer.

    Jetzt sagst Du das einzige Zeichen mit Codepoint >0xff ist das Euro-Zeichen, also reicht im Grunde ein std::istream bzw. ein std::streambuf mit char_type=char aus. Und das Euro-Zeichen wird einfach in den Bereich <0x100 untergebracht (bei 0xA4 wie z.B. in Latin-9).

    Vorher definiere ich noch ein paar Konstanten:

    // --   ein paar Konstanten
    namespace unicode
    {
        namespace
        {
            const wchar_t EUR(0x20AC);
        }
    }
    namespace latin9
    {
        namespace
        {
            const char EUR(0xA4);  // so wie in: ISO 8859-15 (ISO-Latin-9), ISO 8859-16 (ISO-Latin-10), ISO 8859-7
            const char POUND(0xA3); // Code identisch mit unicode::POUND
        }
    }
    

    und dann den Streambuf

    #include <istream>
    #include <string>
    #include <streambuf>
    
    // --   IStreambuf< char > der aus einem QString liest
    class MyBuf : public std::streambuf
    {
    public:
        MyBuf( const QString& source )
            : source_( source )
            , cur_( source.begin() )
        {}
    protected:
        virtual int_type uflow()
        {
            if( cur_ == source_.end() )
                return traits_type::eof(); // String Ende
            return cvt( *cur_++ );
        }
        virtual int_type underflow()
        {
            if( cur_ == source_.end() )
                return traits_type::eof(); // String Ende
            return cvt( *cur_ );
        }
    private:
        int_type cvt( QChar q ) // Methode 'konvertiert' ein QChar zu einem char
        {
            if( q == QChar() )
                return traits_type::eof(); // ends !? scheint eine QString-Besonderheit zu sein
            char_type ret = char_type( q.unicode() );
            switch( q.unicode() )
            {
            case unicode::EUR: // convertiere EUR-Code
                ret = latin9::EUR;
                break;
            default:
                if( QChar( ret ) != q )
                    return traits_type::eof(); // Fehler, da keine 8Bit
                break;
            }
            return traits_type::to_int_type( ret );
        }
        QString source_;
        QString::const_iterator cur_;
    };
    
    class MyIStream : public std::istream
    {
    public:
        MyIStream( const QString& source )
            : std::istream( &buf_ )
            , buf_( source )
        {}
    private:
        MyBuf buf_;
    };
    

    Dieser Streambuf liest alle Zeichen mit einem Code <=0xff aus dem QString und wenn ein Eurozeichen drin ist, so wird es nach 0xA4 konvertiert. Tritt ein anderes Zeichen auf, so liefert er EOF.

    Mit der Klasse MyIStream hast Du nun eine standardisierte Schnittstelle (std::istream), wo Du Zahlen lesen kann, auf die man auch den Manipulator std::get_money oder einen eigenen Währungstypleser drauf los lassen kannst.

    // --   Währungs-enum
    namespace currency
    {
        enum Type { Euro, Pound };
    }
    std::istream& operator>>( std::istream& in, currency::Type& c )
    {
        char x;
        if( in >> x )
        {
            switch( x )
            {
            case latin9::POUND: c = currency::Pound;  break;
            case latin9::EUR: c = currency::Euro; break;
            default:
                in.setstate( std::ios_base::failbit );
                break;
            }
        }
        return in;
    }
    

    Falls Du einen Text in einen enum umwandeln willst, so empfehle ich Dir wärmstens _Getloctxt aus der Dinkumware-locale-Implementierung - den Hinweis hatte ich schon mal gepostet.

    Gruß
    Werner



  • Hi,

    das gefällt mir schon Mal sehr gut, vielen Dank!! 🙂

    Eine Frage hätte ich noch:

    C++ tut dies in der Facette std::codecvt und in C++11 sind konkrete Derivate definiert, die die ganze UTF-Familie abdecken (z.B.: http://www.cplusplus.com/reference/codecvt/codecvt_utf8).
    Irritierender Weise erwähnt das Proposal das mit keinem Wort.

    Bedeutet das jetzt, dass man basic_istream<char32_t> in Verbindung mit einem Codec nutzen kann, sodass man UTF8-Support hat und auch mit [], Iteratoren, Splits und allen Späßen ganz normal arbeiten kann? Oder zählt das immer noch nicht als voller Unicode-Support? Man muss halt die ganze Software auf den basic_istream<char32_t> umstellen und immer die locale setzen, aber wenn man das tut, hat man dann nicht den UTF-8-Support, den wir uns so sehr wünschen?

    Von back2basics wurde ja auch u32string vorgeschlagen, aber darauf hat keiner kommentiert.



  • Eisflamme schrieb:

    Eine Frage hätte ich noch:

    C++ tut dies in der Facette std::codecvt und in C++11 sind konkrete Derivate definiert, die die ganze UTF-Familie abdecken (z.B.: http://www.cplusplus.com/reference/codecvt/codecvt_utf8).
    Irritierender Weise erwähnt das Proposal das mit keinem Wort.

    Bedeutet das jetzt, dass man basic_istream<char32_t> in Verbindung mit einem Codec nutzen kann, sodass man UTF8-Support hat und auch mit [], Iteratoren, Splits und allen Späßen ganz normal arbeiten kann? Oder zählt das immer noch nicht als voller Unicode-Support? Man muss halt die ganze Software auf den basic_istream<char32_t> umstellen und immer die locale setzen, aber wenn man das tut, hat man dann nicht den UTF-8-Support, den wir uns so sehr wünschen?

    Von back2basics wurde ja auch u32string vorgeschlagen, aber darauf hat keiner kommentiert.

    Hallo Eisflamme,

    naja, definiere, was irgendjemand unter 'vollen Unicode-Support' versteht.

    Ja - grundsätzlich kannst Du das natürlich tun und deckst mit char32_t den gesamten Bereich der Unicode-Codepoints ab. Problematisch ist das, wenn char32_t nicht identisch mit wchar_t , sondern z.B. ein typedef unsigned int ist. Dann kann der Compiler nicht mehr unterscheiden, ob Du ein ein Zeichen oder eben einen unsigned int lesen möchtest. Eines von beiden funktioniert dann nicht mehr.
    Weiter setzt es voraus, dass die verwendete Facetten auch alle für diesen Datentyp implementiert sind.

    In dem von Dir beschriebenen Fall halte ich das für overkill und gar nicht notwendig. Es löst Probleme, die Du nicht hast, dafür handelst Du Dir ggf. neue ein.

    Gruß
    Werner



  • Werner Salomon schrieb:

    Problematisch ist das, wenn char32_t nicht identisch mit wchar_t , sondern z.B. ein typedef unsigned int ist. Dann kann der Compiler nicht mehr unterscheiden, ob Du ein ein Zeichen oder eben einen unsigned int lesen möchtest. Eines von beiden funktioniert dann nicht mehr.

    Types char16_t and char32_t denote distinct types with the same size, signedness, and alignment as uint_least16_t and uint_least32_t, respectively, in <stdint.h>, called the underlying types. (3.9.1/5)

    Weiter setzt es voraus, dass die verwendete Facetten auch alle für diesen Datentyp implementiert sind.

    Almost every(!) facet has design flaws.

    Der Artikel http://www.utf8everywhere.org/ und insbesondere der Vorschlag http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3572.html unterscheiden nur wenig oder gar nicht.
    Was das Proposal ignoriert ist die Tatsache, dass der C++-Standard schon sehr genau zwischen externen Bytes (oder Words) und internen Zeichen unterscheidet. C++ tut dies in der Facette std::codecvt und in C++11 sind konkrete Derivate definiert, die die ganze UTF-Familie abdecken (z.B.: http://www.cplusplus.com/reference/codecvt/codecvt_utf8).
    Irritierender Weise erwähnt das Proposal das mit keinem Wort.

    In particular, we believe that adding wchar_t to C++ was a mistake, and so are the Unicode additions to C++11.

    // Fehler, da keine 8Bit

    Toll, 100 Zeile Code nur um "€" durch ein 8-Bit-Zeichen zu ersetzen. Das ist kein Unicode, das ist ein ASCII-Hack.
    Um Strings zu parsen nimmt man const char* , das ist 1. schneller (strstr ftw), 2. mächtiger (manchmal will man mehr als nur 1 Zeichen im Voraus wissen), 3. weniger Code und vor allem 4. Unicode-aware.

    Nun ist es aber so, dass UTf-8/16 so designend ist, dass eine Folge von char, bei denen jedes <=0x7f ist, gleichzeitig Unicode und gültiges UTF-8 sei kann. Das gleiche gilt für eine String aus wchar_t mit sizeof(wchar_t)==2 mit Zeichen deren Code <=0x7fff; [...] ein paar Umlaute, die falsch dargestellt werden, stören nicht wirklich.

    Das erscheint mir äussert kurzsichtig. Es gibt kein "bisschen Unicode". Es gibt nur vollständigen Unicode-Support oder keinen Unicode-Support.
    Vollständigen Unicode-Support kann man erreichen, in dem man entweder wie von utf8everywhere.org empfohlen UTF-8 verwendet und sich nur an Zeichen <=127 orientiert (splittet, ersetzt, parst) und den Rest unbesehen weiterleitet oder indem Unicode vollständig verstanden wird. Zweiteres ist äusserst schwierig, da es Fälle wie " ¨y " gibt, bei denen zwei Codepoints ein Zeichen " ÿ " darstellen. Splitten darf man also nicht einfach so, weder mit wstring noch mit u32string.

    Im Gegebenen Fall von Eisflamme heisst das also std::string mit UTF-8 zu verwenden.

    const char *ptr; // das, was geparst wird
    const std::string euro = u8"€";
    if (std::equal(euro.begin(), euro.end(), ptr)
      // es handelt sich um ein Euro-Zeichen
    

Anmelden zum Antworten