Как я могу использовать std:: imbue для установки локали для std:: wcout?
Я пытаюсь использовать механизм std::locale
в С++ 11 для подсчета слов на разных языках. В частности, у меня есть std::wstringstream
, который содержит название знаменитого русского романа ( "Преступление и наказание" на английском языке). Я хочу использовать соответствующий язык (ru_RU.utf8
на моей машине Linux) для чтения строкового потока, подсчета слов и печати результатов. Я также должен заметить, что моя система настроена на использование локали en_US.utf8
.
Желаемый результат:
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
на #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";
}
Ответы
Ответ 1
В этом ответе я беру вопросы в обратном порядке и добавляю еще один (с ответом), который появился на этом пути.
Есть ли способ использовать imbue
вместо того, чтобы устанавливать глобальную локаль, чтобы делать то, что я хочу?
Да. По умолчанию std::wcout
синхронизируется с базовым потоком stdout
C. Поэтому std::wcout
может использовать imbue
, если эта синхронизация отключена, позволяя потоку С++ работать независимо. Поэтому, чтобы изменить исходный код для использования 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>
Кроме того, без явной установки глобальной локали, locale является языковым стандартом "C"
, который является US English ASCII, поэтому это означает, что stdout
будет по умолчанию иметь ASCII-сопоставление. Поскольку в ASCII не представлены кириллические символы, базовый stdout
- это то, что преобразует правильный русский в последовательность символов ?
.
Почему перед вызовом sync_with_stdio
предшествует imbue
?
В соответствии с 27.5.3.4 стандарта:
Если какая-либо операция ввода или вывода произошла с использованием стандартных потоков до вызова, эффект определяется реализацией. В противном случае, вызываемый с ложным аргументом, он позволяет стандартным потокам работать независимо от стандартных потоков C.
Ответ 2
Сначала я сделал еще несколько тестов, используя ваш код, и я могу подтвердить, что 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
, которая ничего не знает о юникоде. Все печатные символы ASCII (ниже 128) выводятся как есть, а другие заменяются на ?
. Это именно то, что делает ваша программа.
Чтобы он работал правильно, вам нужно выбрать локаль, которая знает о символах Unicode с помощью setlocale
. Как только это будет сделано, вы можете изменить числовое преобразование, вызвав imbue
, и поскольку вы выбрали кодировку unicode, все будет в порядке.
Итак, если ваш текущий язык использует кодировку 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, "");
. Он автоматически адаптируется к текущей кодировке. Я тестировал с урезанным вариантом с использованием латинского набора символов (моя система говорит по-французски, а не по-русски...)
#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 console) и получил èé
на консоли.
Изменить 2:
Конечно, вы также можете установить глобальную локаль С++ с locale::global(locale("");
с локалью по умолчанию или locale::global(locale("ru_RU.UTF-8");
с русской локалью, но это больше, чем просто вызов setlocale
. Согласно документации по реализации Gnu стандартной библиотеки С++ о locale: существует только одно отношение (языкового механизма С++) к языковому механизму C: глобальное C изменен, если названный объект локали С++ задан как глобальная локаль ", то есть: std::locale::global(std::locale(""));
влияет на функции C, как если бы был выполнен следующий вызов: std::setlocale(LC_ALL, "");
. С другой стороны, нет наоборот, то есть вызов setlocale не имеет никакого отношения к языковому механизму С++, в частности по работе с locale (" ").
Итак, похоже, что существует базовый механизм библиотеки C, который должен быть сначала включен с помощью setlocale
, чтобы позволить преобразованию imbue
работать правильно.
Ответ 3
Я не знаю, на каких языках вы планируете поддерживать, но есть языки, где ваш алгоритм не применяется, например. Японский. Я предлагаю проверить итераторы слова в международных компонентах для Unicode. http://userguide.icu-project.org/boundaryanalysis