Как я могу использовать 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) я получаю ошибку, упомянутых в это другой вопрос.
те, кто заинтересован в экспериментировании с кодом или с настройками компилятора или оба могут использовать этот живой код.
мои вопросы
- почему это не работает? Это потому что
wcoutуже открыт? - есть ли способ, чтобы использовать
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