Как я могу использовать std::imbue для установки локали для std:: wcout?

Я пытаюсь использовать std::locale механизм в C++11 для подсчета слов в разных языках. В частности, у меня std::wstringstream который содержит название известного русского романа ("Преступление и наказание" на английском языке). То, что я хочу сделать, это использовать соответствующую локаль (ru_RU.utf8 на моей машине Linux) чтобы прочитать stringstream, подсчитайте слова и распечатайте результаты. Я также должен отметить, что моя система настроена на использование en_US.utf8 locale.

желаемый результат это:

0: "Преступление"
1: "и"
2: "наказание"

I counted 3 words.
and the last word was "наказание"

это все работает, когда я устанавливаю глобальную локаль, но не когда я пытаюсь imbue на wcout поток. Когда я пробую это, я получаю этот результат вместо этого:

0: "????????????"
1: "?"
2: "?????????"

I counted 3 words.
and the last word was "?????????"

также, когда я пытаюсь использовать решение, предложенное в комментариях (которое можно активировать, изменив #define USE_CODECVT 0 to #define USE_CODECVT 1) я получаю ошибку, упомянутых в это другой вопрос.

те, кто заинтересован в экспериментировании с кодом или с настройками компилятора или оба могут использовать этот живой код.

мои вопросы

  1. почему это не работает? Это потому что wcout уже открыт?
  2. есть ли способ, чтобы использовать imbue вместо того, чтобы устанавливать глобальную локаль, чтобы делать то, что я хочу?

если это имеет значение, я использую g++ 4.8.3. Полный код показан ниже.

getwords.cpp

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <locale>

#define USE_CODECVT 0
#define USE_IMBUE   1

#if USE_CODECVT
#include <codecvt>
#endif 
using namespace std;

int main()
{
#if USE_CODECVT
    locale ru("ru_RU.utf8", 
        new codecvt_utf8<wchar_t, 0x10ffff, consume_header>{});
#else
    locale ru("ru_RU.utf8");
#endif
#if USE_IMBUE
    wcout.imbue(ru);
#else
    locale::global(ru);
#endif
    wstringstream in{L"Преступление и наказание"};
    in.imbue(ru);
    wstring word;
    unsigned wordcount = 0;
    while (in >> word) {
        wcout << wordcount << ": "" << word << ""n";
        ++wordcount;
    }
    wcout << "nI counted " << wordcount << " words.n"
        << "and the last word was "" << word << ""n";
}

3 ответов


сначала я сделал еще несколько тестов, используя ваш код, и я могу это подтвердить L"Преступление и наказание" является правильной строкой UTF16. Я контролировал код отдельных символов, и они правильно 0x41f, 0x440, 0x435, 0x441, 0x442, 0x443, 0x43f, 0x43b, 0x435, 0x43d, 0x438, 0x435, 0x20, 0x438, 0x20, 0x43d, 0x430, 0x43a, 0x430, 0x437, 0x430, 0x43d, 0x438, 0x435

я не мог найти никакой ссылки об этом, но это похоже на простой вызов imbue не хватает. imbue это метод от basic_ios, который является предком cout и wcout. Он действует на числовые преобразования, но на всех моих тестах он не влияет на кодировку, используемую для выход.

по умолчанию языковой стандарт, используемый в программе C++ (или C)... the C locale, который ничего не знает о unicode. Все печатаемые символы ASCII (ниже 128) выводятся как есть, а другие заменяются ?. Это именно то, что делает ваша программа.

чтобы он работал правильно, вы должны выбрать локаль, которая знает о символах Юникода с setlocale. Как только это будет сделано, вы можете изменить числовое преобразование, вызвав imbue, и как вы выбрали кодировку Юникод все будет хорошо.

таким образом, если ваш текущий язык использует кодировку UTF-8, вам нужно только добавить

setlocale(LC_ALL, "");

как первая строка в вашей программе, и выход будет, как ожидалось:

0: "Преступление"
1: "и"
2: "наказание"

I counted 3 words.
and the last word was "наказание"

если ваша текущая локаль не использует UTF-8, выберите тот, который установлен на Вас системы и поддерживает его. Я использовал setlocale(LC_ALL, "fr_FR.UTF-8");, или даже setlocale(LC_ALL, "en_US.UTF-8"); и оба работали.

изменить :

на самом деле, лучший способ правильно вывести unicode на экран-использовать setlocale(LC_ALL, "");. Он автоматически адаптируется к текущей кодировке. Я тестировал с урезанным вариантом, используя кодировку Latin1 (моя система говорит по-французски, а не по-русски ...)

#include <iostream>
#include <locale>

using namespace std;

int main() {
    setlocale(LC_ALL, "");
    wchar_t ws[] = { 0xe8, 0xe9, 0 };

    wcout << ws << endl;
}

я пробовал его под Linux, используя кодировку UTF-8 и ISO-8859-1 (latin1) (resp export LANG=fr_FR.UTF-8 и export LANG=fr_FR.ISO-8859-1) и я получил правильно èé в правильной кодировке. Я попробовал это также под Windows XP, с кодовой страницей 851 (oem) и 1252 (ansi) (соотв. chcp 850 и chcp 1252 С консольной кодировкой Lucida), и получил èé на консоли тоже.

Edit 2:

конечно, вы также можете установить глобальную локаль C++ с locale::global(locale(""); С локали по умолчанию или locale::global(locale("ru_RU.UTF-8"); С русским языком, но это больше, чем просто вызов setlocale. Согласно документации реализации Gnu стандартной библиотеки C++ о locale : существует только одно отношение (языкового стандарта C++ механизм) к механизму локали C: глобальная локаль C изменяется, если именованный объект локали c++ задан как глобальная локаль", то есть: std::locale::global(std::locale("")); влияет на функции C, как если бы был сделан следующий вызов:std::setlocale(LC_ALL, "");. С другой стороны, нет наоборот, то есть вызов setlocale не имеет никакого отношения к механизму локали C++, в частности к работе locale("").

таким образом, действительно похоже, что была базовая библиотека c mechanizme, которая должна быть первой включена setlocale разрешить imbue преобразования для правильной работы.


в этом ответе я беру вопросы в обратном порядке и добавляю еще один (с ответом), который появился на этом пути.

есть ли способ, чтобы использовать imbue вместо того, чтобы устанавливать глобальную локаль, чтобы делать то, что я хочу?

да. по умолчанию std::wcout синхронизируется с базовым stdout С потока. Так что std::wcout can использовать imbue если эта синхронизация отключена, позволяя потоку C++ работать независимо. Так измените исходный код для использования imbue и работать по назначению только одна строка должна быть добавлена, вызывая std::ios_base::sync_with_stdio:

std::ios_base::sync_with_stdio(false);
std::wcout.imbue(ru);

почему оригинальная версия не работает?

стандарт (я имею в виду INCITS/ISO/IEC 14882-2011[2012]) говорит очень мало о связи с основным stdio потоком, но в 27.4.3 он говорит

объект wcout управляет выводом в буфер потока, связанный с объектом stdout, объявлено в <cstdio>

далее, без явного задания глобальной локали, локаль является "C" locale, который является US English ASCII, так что это, по-видимому, означает, что stdout по умолчанию будет иметь отображение ASCII. Поскольку кириллические символы не представлены в ASCII, базовый stdout это то, что превращает правильный русский язык в серию ? символы.

почему sync_with_stdio звонок перед imbue?

по данным 27.5.3.4 стандарта:

если какая-либо операция ввода или вывода произошла с использованием стандартных потоков до вызова, эффект определяется реализацией. В противном случае, вызываемый с ложным аргументом, он позволяет стандартным потокам работать независимо от стандартных потоков C.


Я не знаю, какие языки вы собираетесь поддерживать, но есть языки, где ваш алгоритм не применяется, напр. Японский. Я предлагаю проверить итераторы слов в международных компонентах для Unicode. http://userguide.icu-project.org/boundaryanalysis