Konvertierung zwischen std :: wstring und std :: string

74958
Jere.Jones

Während Möglichkeiten der Erforschung hin und her zwischen umwandeln std::wstringund std::stringfand ich dieses Gespräch auf den MSDN - Foren.

Es gab zwei Funktionen, die für mich gut aussahen. Insbesondere diese:

std::wstring s2ws(const std::string& s)
{
    int len;
    int slength = (int)s.length() + 1;
    len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0); 
    wchar_t* buf = new wchar_t[len];
    MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len);
    std::wstring r(buf);
    delete[] buf;
    return r;
}

std::string ws2s(const std::wstring& s)
{
    int len;
    int slength = (int)s.length() + 1;
    len = WideCharToMultiByte(CP_ACP, 0, s.c_str(), slength, 0, 0, 0, 0); 
    char* buf = new char[len];
    WideCharToMultiByte(CP_ACP, 0, s.c_str(), slength, buf, len, 0, 0); 
    std::string r(buf);
    delete[] buf;
    return r;
}

Die doppelte Zuordnung und die Notwendigkeit, den Puffer zu löschen, betreffen mich jedoch (Leistung und Ausnahmesicherheit). Daher habe ich sie wie folgt geändert:

std::wstring s2ws(const std::string& s)
{
    int len;
    int slength = (int)s.length() + 1;
    len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0); 
    std::wstring r(len, L'\0');
    MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, &r[0], len);
    return r;
}

std::string ws2s(const std::wstring& s)
{
    int len;
    int slength = (int)s.length() + 1;
    len = WideCharToMultiByte(CP_ACP, 0, s.c_str(), slength, 0, 0, 0, 0); 
    std::string r(len, '\0');
    WideCharToMultiByte(CP_ACP, 0, s.c_str(), slength, &r[0], len, 0, 0); 
    return r;
}

Unit-Tests zeigen an, dass dies in einer schönen, kontrollierten Umgebung funktioniert, aber ist dies in der bösartigen und unvorhersehbaren Welt des Computers meines Kunden in Ordnung?

Antworten
32

9 Antworten auf die Frage

11

Ich würde und habe Ihren Satz von Funktionen so umgestaltet, dass er den Abgüssen ähnelt:

std::wstring x;
std::string y = string_cast<std::string>(x);

Dies kann später viele Vorteile haben, wenn Sie sich mit der Vorstellung einer Drittanbieter-Bibliothek darüber beschäftigen müssen, wie Strings aussehen sollen.

Ich liebe die Syntax. Kannst du den Code teilen? Jere.Jones vor 9 Jahren 1
Oooh Das sieht gut aus. Wie würde man das machen? Erstellen Sie einfach eine Vorlage mit Spezialisierungen, um zwischen den verschiedenen Zeichenfolgentypen zu konvertieren. Billy ONeal vor 9 Jahren 1
@Billy jemand hat eine Codereview-Frage für die Implementierung von string_cast [hier] (http://codereview.stackexchange.com/questions/1205/c-string-cast-template-function/1466#1466) veröffentlicht, wenn Sie daran interessiert sind. greatwolf vor 8 Jahren 0
9
AndiDog

Actually my unit testing shows that your code is wrong!

The problem is that you include the zero terminator in the output string, which is not supposed to happen with std::string and friends. Here's an example why this can lead to problems, especially if you use std::string::compare:

// Allocate string with 5 characters (including the zero terminator as in your code!)
string s(5, '_');

memcpy(&s[0], "ABCD\0", 5);

// Comparing with strcmp is all fine since it only compares until the terminator
const int cmp1 = strcmp(s.c_str(), "ABCD"); // 0

// ...however the number of characters that std::string::compare compares is
// someString.size(), and since s.size() == 5, it is obviously not equal to "ABCD"!
const int cmp2 = s.compare("ABCD"); // 1

// And just to prove that string implementations automatically add a zero terminator
// if you call .c_str()
s.resize(3);
const int cmp3 = strcmp(s.c_str(), "ABC"); // 0
const char term = s.c_str()[3]; // 0

printf("cmp1=%d, cmp2=%d, cmp3=%d, terminator=%d\n", cmp1, cmp2, cmp3, (int)term);
Ich fand auch das Hinzufügen des Terminators ärgerlich: In meinem Fall brach es auch einen String-Zusatz. Am Ende habe ich den booleschen Parameter `includeTerminator` zu beiden Methoden hinzugefügt. reallynice vor 4 Jahren 0
6
Ferruccio

Ein Problem, das möglicherweise ein Problem darstellt, besteht darin, dass die Zeichenfolge ANSI-formatiert ist, wobei die derzeit aktive Codepage (CP_ACP) verwendet wird. Möglicherweise möchten Sie eine bestimmte Codepage oder CP_UTF8 verwenden, wenn es sich um UTF-8 handelt.

Dies kann eine dumme Frage sein, aber wie kann ich das sagen? Für meine Verwendung sind dies normalerweise Dateinamen. Jere.Jones vor 9 Jahren 0
Wie erhalten Sie die Dateinamen? Dies bestimmt die richtige zu verwendende Codepage. Ferruccio vor 9 Jahren 0
@ Jere.Jones: Eine Möglichkeit besteht darin, zu prüfen, ob die Zeichenfolge ein gültiges UTF-8 ist. Wenn nicht, nehmen Sie an, es ist ANSI. dan04 vor 9 Jahren 0
@ dan04: ANSI erfordert, dass eine Codeseite angegeben wird. http://en.wikipedia.org/wiki/Code_page. Ferruccio vor 9 Jahren 0
Weitere Hinweise: In der [MSDN-Dokumentation] (https://msdn.microsoft.com/de-de/library/windows/desktop/dd374130 (v = vs.85) .aspx) wird empfohlen, CP_ACP nicht für Zeichenfolgen zu verwenden, die für die dauerhafte Speicherung vorgesehen sind , weil die aktive Seite jederzeit geändert werden kann M.M vor 2 Jahren 0
CP_UTF8 https://docs.microsoft.com/es-es/windows/desktop/Intl/code-page-identifiers Joma vor einem Jahr 0
4
Roddy

Ich würde empfehlen, dies zu ändern:

int len;
int slength = (int)s.length() + 1;
len = WideCharToMultiByte(CP_ACP, 0, s.c_str(), slength, 0, 0, 0, 0);

... dazu:

int slength = (int)s.length() + 1;
int len = WideCharToMultiByte(CP_ACP, 0, s.c_str(), slength, 0, 0, 0, 0);

Etwas prägnanter, lender Geltungsbereich wird eingeschränkt, und Sie haben keine uninitialisierte Variable, die sich (nur für eine Zeile) als Falle für Unvorsichtige eignet.

4
Frank

Es hängt wirklich davon ab, welche Codecs mit std::wstringund verwendet werden std::string.

Diese Antwort setzt voraus, dass das std::wstringeine UTF-16-Codierung verwendet und dass bei der Konvertierung in std::stringeine UTF-8-Codierung verwendet wird.

#include <codecvt>
#include <string>

std::wstring utf8ToUtf16(const std::string& utf8Str)
{
    std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> conv;
    return conv.from_bytes(utf8Str);
}

std::string utf16ToUtf8(const std::wstring& utf16Str)
{
    std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> conv;
    return conv.to_bytes(utf16Str);
}

Diese Antwort verwendet die STL und ist nicht auf eine plattformspezifische Bibliothek angewiesen.

Dies ist die beste Antwort, da nur das funktioniert. Vielen Dank. Roman vor 2 Jahren 0
2
Mark Loeser

Ich mache keine Windows-Entwicklung, daher kann ich nicht den WideCharToMultiBytesicheren Teil kommentieren .

Das einzige, was ich sagen würde, ist sicherzustellen, dass Sie für alles die richtigen Typen verwenden. Gibt beispielsweise string.length()ein zurück std::string::size_type(höchstwahrscheinlich ein size_t, der Konstruktor nimmt auch ein std::string::size_type, aber das ist nicht so viel von einem Deal). Es wird Sie wahrscheinlich nie beißen, aber es ist etwas, worauf Sie achten sollten, um sicherzustellen, dass Sie keinen Überlauf in anderem Code haben, den Sie schreiben.

Nun, es gibt ein `std :: string :: size_type` zurück. Jon Purdy vor 9 Jahren 1
@Jon: Richtig, aber ich habe noch nie gesehen, dass es nicht der Darstellung eines `size_t` entspricht. Ich werde die Antwort jedoch ändern, danke für Ihr Feedback. Mark Loeser vor 9 Jahren 0
@Jon: `std :: string :: size_type` ist immer ein` std :: size_t`. GManNickG vor 9 Jahren 2
@GMan: Ich war nur aus Langeweile pedantisch. SGI sagt, es sei "ein vorzeichenloser integraler Typ, der jeden nicht-negativen Wert des Abstandstyps des Containers darstellen kann", d. H. "Difference_type" - und diese beiden müssen "Typedef" für vorhandene Typen sein, dies ist jedoch nicht der Fall Das bedeutet nicht, dass `size_type` gleichbedeutend mit` size_t` sein muss. Gibt es hier noch etwas anderes? Jon Purdy vor 9 Jahren 0
@ Jon: Ich bin mir nicht sicher, warum SGI wichtig ist. Der * Standard * sagt, dass `std :: string :: size_type` ʻallocator_type :: size_type` ist, und der` size_type` des Defaultallokators ist `std :: size_t`. GManNickG vor 9 Jahren 0
@GMan: Okay, danach habe ich gesucht. SGI spielt keine Rolle, außer natürlich, wenn dies der Fall ist. Aber hey, `size_type` ist der erste in der Kette, wenn es darauf ankommt. Oder das letzte, je nachdem wo du stehst. Jon Purdy vor 9 Jahren 0
2
txtechhelp

Hier ist eine plattformübergreifende Version, die ich für ein Framework geschrieben habe, an dem ich gerade arbeite. Es verwendet die UTF8-Codepage, kann sich aber bei Bedarf jederzeit ändern. Dies ist eine abgespeckte Version, da sie nicht alle expliziten Makrodefinitionen enthält, aber Sie können die Idee davon erhalten:

#if defined(OMNI_OS_WIN)
    #include <windows.h>
#endif
#include <cctype>
#include <cwctype>
#include <string>
// not sure if these are all needed .. haven't had my midnight coffee :)

std::string omni::string::to_string(const std::wstring& str)
{
    size_t sz = str.length();
    #if defined(OMNI_OS_WIN)
        int nd = WideCharToMultiByte(CP_UTF8, 0, &str[0], sz, NULL, 0, NULL, NULL);
        std::string ret(nd, 0);
        int w = WideCharToMultiByte(CP_UTF8, 0, &str[0], sz, &ret[0], nd, NULL, NULL);
        if (w != sz) {
            #if defined(OMNI_THROW_ON_ERR)
                throw omni::string_exception("Invalid size written");
            #else
                OMNI_ERR_RETV("");
            #endif
        }
        return ret;
    #else
        const wchar_t* p = str.c_str();
        char* tp = new char[sz];
        size_t w = wcstombs(tp, p, sz);
        if (w != sz) {
            delete[] tp;
            #if defined(OMNI_THROW_ON_ERR)
                throw omni::string_exception("Invalid size written");
            #else
                OMNI_ERR_RETV("");
            #endif
        }
        std::string ret(tp);
        delete[] tp;
        return ret;
    #endif
}

std::wstring omni::string::to_wstring(const std::string& str)
{
    #if defined(OMNI_OS_WIN)
        size_t sz = str.length();
        int nd = MultiByteToWideChar(CP_UTF8, 0, &str[0], sz, NULL, 0);
        std::wstring ret(nd, 0);
        int w = MultiByteToWideChar(CP_UTF8, 0, &str[0], sz, &ret[0], nd);
        if (w != sz) {
            #if defined(OMNI_THROW_ON_ERR)
                throw omni::string_exception("Invalid size written");
            #else
                OMNI_ERR_RETV(L"");
            #endif
        }
        return ret;
    #else
        const char* p = str.c_str();
        size_t len = str.length();
        size_t sz = len * sizeof(wchar_t);
        wchar_t* tp = new wchar_t[sz];
        size_t w = mbstowcs(tp, p, sz);
        if (w != len) {
            delete[] tp;
            #if defined(OMNI_THROW_ON_ERR)
                throw omni::string_exception("Invalid size written");
            #else
                OMNI_ERR_RETV(L"");
            #endif
        }
        std::wstring ret(tp);
        delete[] tp;
        return ret;
    #endif
}

Hier ist ein Beispiel dafür:

std::string s = "here's a standard string";
std::wstring w = L"here's a wide string";
std::string sw = omni::string::to_string(w);
std::wstring ws = omni::string::to_wstring(s);

std::cout << "s = " << s << std::endl;
std::wcout << "w = " << w << std::endl;
std::cout << "sw = " << sw << std::endl;
std::wcout << "ws = " << ws << std::endl;

Hoffe das kann jemandem helfen.

0
Jonathan Wood

Ich habe nur kurz Ihren Code überflogen. Ich habe nicht viel mit std :: string gearbeitet, aber ich habe viel mit der API gearbeitet.

Angenommen, Sie haben alle Ihre Längen und Argumente richtig (manchmal stellen Sie sicher, dass der Terminator und die Breite vs. Multibyte-Längen in Ordnung sind, kann schwierig sein), glaube ich, dass Sie auf dem richtigen Weg sind. Ich denke, die ersten Routinen, die Sie gebucht haben, weisen unnötigerweise einen zusätzlichen Puffer zu. Es ist nicht nötig.

-3
user605592

Nein, das ist gefährlich! Die Zeichen in einem std :: string dürfen nicht in einem zusammenhängenden Speicherblock gespeichert sein und Sie dürfen den Zeiger nicht &r[0]zum Schreiben auf andere Zeichen als dieses Zeichen verwenden! Deshalb gibt die c_str()Funktion einen constZeiger zurück.

Es funktioniert möglicherweise mit MSVC, wird aber wahrscheinlich unterbrochen, wenn Sie zu einem anderen Compiler oder einer anderen STL-Bibliothek wechseln.

-1: Falsch: http://stackoverflow.com/questions/2256160/how-bad-is-code-using-stdbasic-stringt-as-a-contiguous-buffer Billy ONeal vor 8 Jahren 2