Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Ввод\вывод Unicode в C++


Автор: shaukote 6.4.2013, 13:38
Всем доброго времени суток.

Буду очень благодарен, если кто-нибудь здесь сможет мне помочь.

Сначала кратко опишу ситуацию. 
Являясь студентом технического вуза, я постоянно пишу на C++ различные программы различной степени сложности, в основном - консольные. При этом я склонен работать с Unicode (wchar_t, L" ... ", std::wstring, std::wcin, std::wcout etc) и храню исходники в UTF-8, благо Sublime делает это по умолчанию.
Чаще всего я работаю под Windows 7, соответственно компилирую свои поделия MinGW и запускаю их в cmd.exe.
Однако иногда у меня возникает потребность сделать это в Linux, соответственно, с gcc и местной консолью.
Соответственно, мне нужно как-то обеспечивать корректный ввод/вывод Unicode в своих программах. При чём желательно сделать это стандартными кроссплатформенными средствами.

Теперь как я решал эту ситуацию.  
До этого момента я просто помещал в начало программы вызов 
Код
setlocale(LC_ALL, "");
 и всё как-то работало.
Однако с недавнего момента перестало. 
На протяжении последних дней я безуспешно пытался выяснить, в чем проблема, и в это время ввод/вывод то не работал, при этом ломаясь в самых странных местах.
Прозвучит, глупо, возможно, но возникло ощущение, что оно зависит "от фазы Луны".
Так что, судя по всему, этот метод не работает.

Я решил посоветоваться со Страуструпом - он в своей книге рекомендует использовать класс std::locale. Что же, пробуем:
Код

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

using std::locale;
using std::wcin;
using std::wcout;
using std::endl;
using std::wstring;
using std::wistringstream;

int main(int argc, char const *argv[])
{
    locale russian("ru_RU.UTF-8");
    wcin.imbue(russian);
    wcout.imbue(russian);
    
    wstring text, word;
    getline(wcin, text);
    wcout << text << endl;
     
    wistringstream input(text);

    while (input >> word)
        wcout << word << endl;


    return 0;
}

Во время выполнения получаем runtime_error, что говорит о том, что такой локали нету. :(

Скажите пожалуйста, как корректно обеспечить ввод/вывод Unicode?

Автор: Alexeis 6.4.2013, 14:16
В бусте есть затычка для ввода вывода юникода http://www.boost.org/doc/libs/1_33_1/libs/serialization/doc/codecvt.html

Автор: shaukote 6.4.2013, 14:20
Но ведь и в STD есть (вроде бы как) средства для этого - те же wcin/wcout. Неужели нельзя обойтись без сторонних библиотек?

Автор: Alexeis 6.4.2013, 15:39
wcin/wcout сами не умеют работать с кодировками. Они знают что такое 2х байтовый символ, но по умолчанию интерпретировать его будут через фасет codecvt . Как я понял, фасеты определяют преобразование из внутренней кодировки, которую использует программа во внешнюю которую используют потоки ввода/вывода. Вижу в С++11 ввели уже utf16 фасеты. Можно посмотреть тут.
http://www.cplusplus.com/reference/codecvt/codecvt_utf16/

И еще проблема строчки locale russian("ru_RU.UTF-8"); состоит в том, что на нужной платформе может не оказаться фасета с таким именем в каталоге. Этот идентификатор "ru_RU.UTF-8" линуксового происхождения.  

Автор: shaukote 6.4.2013, 18:19
Эмм.
А можно полюбопытствовать, как люди управляли кодировками ввода/вывода до появления C++11?
Нежели исключительно платформозависимыми средствами?..

Просто задача-то вроде вполне стандартная, как-то же все это делают?..

Автор: Alexeis 6.4.2013, 20:38
shaukote, в свое время я сам задавался этим вопросом. Гугл в основном выдает код типа
Цитата

locale russian("ru_RU.UTF-8"); 

Или же 2й вариант для винды. 

Часто встречаются самописные варианты фасетов. Возможно стараются работать с файлами во внутренней кодировке или средствами языка С.

Довольно неплохое описание нашел тут http://habrahabr.ru/post/107679/
Но автор использует самописные фасеты и рекомендует их для переносимости кода.

Добавлено через 1 минуту и 10 секунд
поскольку буст уже стал почти стандартом, я бы рекомендовал использовать бустовкие фасеты вместо самописных. 

Автор: volatile 6.4.2013, 21:03
Цитата(shaukote @  6.4.2013,  18:19 Найти цитируемый пост)
А можно полюбопытствовать, как люди управляли кодировками ввода/вывода до появления C++11?
Нежели исключительно платформозависимыми средствами?..

имхо, да.

не уверен даже, что в C++11 сделали наконец это на 100%, (не изучал пока этот вопрос)
но до этого, ничем платформонезависимым в юникодных кодировках и не пахло.

shaukote, 
Цитата(shaukote @  6.4.2013,  13:38 Найти цитируемый пост)
 я безуспешно пытался выяснить, в чем проблема, и в это время ввод/вывод то не работал, при этом ломаясь в самых странных местах

Цитата(shaukote @  6.4.2013,  13:38 Найти цитируемый пост)
locale russian("ru_RU.UTF-8");


а что вы хотите с UTF-8 под другому и не бывает.  smile 


Автор: Alexeis 6.4.2013, 21:23
Цитата(volatile @  6.4.2013,  22:03 Найти цитируемый пост)
не уверен даже, что в C++11 сделали наконец это на 100%, (не изучал пока этот вопрос)

  По отзывам в инете работает плохо. Лично я пользую _wfopen и гружу сразу в wstring так чтобы не было никаких конвертаций. Дальше перекодирую уже строки между собой платформозависимыми способами. Но вообще стараюсь работать только с wstring UTF16 без преобразований. Соответственно если файл в той же кодировке то можно хоть бинарно грузить. 
По крайней мере имею только конвертацию строк. Файлы работают одинаково. 

Автор: shaukote 7.4.2013, 00:50
Цитата(volatile @  6.4.2013,  21:03 Найти цитируемый пост)
а что вы хотите с UTF-8 под другому и не бывает.  

Вы это серьёзно? Если да, то можете поподробнее разъяснить почему так?


Цитата(volatile @  6.4.2013,  21:03 Найти цитируемый пост)
не уверен даже, что в C++11 сделали наконец это на 100%, (не изучал пока этот вопрос)
но до этого, ничем платформонезависимым в юникодных кодировках и не пахло.

Цитата(Alexeis @  6.4.2013,  21:23 Найти цитируемый пост)
 По отзывам в инете работает плохо

Печально, конечно же.

Хорошо, позвольте слегка переформулировать вопрос.
Как лучше всего устанавливать кодировку в ввода/вывода в отдельных системах (Windows\Linux).
Т.е., в Linux, наверное, вполне хорошим решением будет и 
Код

locale russian("ru_RU.UTF-8");
wcin.imbue(russian);
wcout.imbue(russian);

А как лучше всего в Windows это делать (начхав на платформонезависимость)?

Добавлено через 2 минуты и 39 секунд
И да, Alexeis, спасибо за ссылки. (: 

Автор: volatile 7.4.2013, 01:31
Цитата(shaukote @  7.4.2013,  00:50 Найти цитируемый пост)
Вы это серьёзно? Если да, то можете поподробнее разъяснить почему так?

Здесь уже был когда-то большой срач диспут по поводу использования UTF-8.
мое имхо (но есть и другие мнения), что UTF-8 не предназначена для активной работы со строками. Эта кодировка для передачи данных по сетям, может быть для хранения, но никак не для активной работы.

Цитата(shaukote @  7.4.2013,  00:50 Найти цитируемый пост)
А как лучше всего в Windows это делать (начхав на платформонезависимость)?

Я строронник этого способа. (хотя у него и есть недостатки). Но у других способов недостатков еще больше.
http://forum.vingrad.ru/forum/topic-335725/kw-wcout-locale-%D0%BA%D0%BE%D0%B4%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0.html

Автор: shaukote 7.4.2013, 17:32
Код

#include <iostream>
#include <io.h>
#include <fcntl.h>
#include <string>

using namespace std;

int main()
{
    _setmode(_fileno(stdout), _O_WTEXT);
    _setmode(_fileno(stdin), _O_WTEXT);

    wstring str;
    getline(wcin, str);
    wcout << str << endl;

    return 0;
}

Не работает с MSVC++ ни в VS, ни в Qt Creator.
Английские буквы выводятся вопросительными знаками, на вводе русских вообще подвисает. :(

Автор: shaukote 7.4.2013, 18:07
В MinGW вообще не компилируется. :(
Код

C:\Users\shau-kote\YandexDisk>g++ test.cpp
test.cpp: В функции <int main()>:
test.cpp:11:22: ошибка: нет декларации <stdout> в этой области видимости
test.cpp:11:28: ошибка: нет декларации <_fileno> в этой области видимости
test.cpp:11:31: ошибка: нет декларации <_O_WTEXT> в этой области видимости
test.cpp:12:22: ошибка: нет декларации <stdin> в этой области видимости

Автор: Alexeis 7.4.2013, 20:43
Странно сейчас проверил на С++ Builder так работает правильно пример безо всяких фасетов
Код

    wstring text, word;
    getline(wcin, text);
    wcout << text << endl;

    wistringstream input(text);
    while (input >> word)
        wcout << word << endl;

Чего только не делал и TCHAR менял тип и кодировку исходника, работает как часы. Жаль нет под рукой других компиляторов. Помниться у меня проблема была именно с файлами. 

Автор: volatile 7.4.2013, 23:37
Цитата(shaukote @  7.4.2013,  17:32 Найти цитируемый пост)
getline(wcin, str);

shaukote, да там консольный ввод не работает. это и есть недостаток, но его можно обойти.
(лично у меня не было задач, где нужно было вводить с консоли, поэтому пока меня это устраивает.)
Все остальное работает.
С файловым вводом вообще проблем никогда не было.

Добавлено через 2 минуты и 43 секунды
более того, в винде кроме вышеназванной фичи вообще больше нет средств для вывода мультиязычного текста в консоль.)
насколько я знаю.

Автор: shaukote 8.4.2013, 01:54
Alexeis, я в шоке, но первая половина и у меня работает. А вот на строковом потоке уже начинается бардак - на некоторых буквах он работает некорректно.


volatile, по приведённой Вами ссылке Вы пишете:
Цитата

Но есть очень простое, но увы, нестандартное решение
100% в винде рашает только недокументированная инструкция
_setmode( _fileno( stdout ), _O_WTEXT ); 
если еще и ввод нужен мультиязыковый, то еще и
_setmode( _fileno( stdin ), _O_WTEXT ); 

Это решает абсолютно все проблемы с юникодом в  вин консоли.

Я же точно так же сделал, почему же у меня ввод не работает?

Цитата(volatile @  7.4.2013,  23:37 Найти цитируемый пост)
более того, в винде кроме вышеназванной фичи вообще больше нет средств для вывода мультиязычного текста в консоль.)
насколько я знаю. 

А странно-полуработающий вариант Alexeis?  smile 

Автор: bsa 8.4.2013, 11:36
http://forum.vingrad.ru/index.php?show_type=forum&showtopic=269794&st=0&anchor=entry2086699

Автор: volatile 8.4.2013, 20:17
Цитата(shaukote @  8.4.2013,  01:54 Найти цитируемый пост)
А странно-полуработающий вариант Alexeis?

имеется ввиду именно мультиязычный текст. не только русский.
Юникод собственно для того и существует, чтобы выводить мультиязычный текст.
Если вам нужен только русский, то да, можете выбирать варианты.

Автор: shaukote 8.4.2013, 21:52
Цитата(bsa @  8.4.2013,  11:36 Найти цитируемый пост)
Русский язык в программах 

Насколько я понял, там речь идёт несколько о другом.


Цитата(volatile @  8.4.2013,  20:17 Найти цитируемый пост)
имеется ввиду именно мультиязычный текст. не только русский.
Юникод собственно для того и существует, чтобы выводить мультиязычный текст.
Если вам нужен только русский, то да, можете выбирать варианты.

Простите, но я вообще не понял, к чему Вы это сказали.  smile 
Мне не дают покоя два вопроса:
1) Почему не работает Ваш вариант с _setmode? Хотя вроде как должен корректно работать и на ввод и на вывод.
2) Как вообще может работать вариант Alexeis?  smile 

Буду крайне признателен, если Вы сможете мне их разъяснить.

Автор: volatile 8.4.2013, 23:38
Цитата(shaukote @  8.4.2013,  21:52 Найти цитируемый пост)
Мне не дают покоя два вопроса:
1) Почему не работает Ваш вариант с _setmode? Хотя вроде как должен корректно работать и на ввод и на вывод.

Нет на ввод он действительно в этом виде не работает . (именно на ручной ввод с консоли)
объясню почему меня это устраивает: я никгда не делаю приложений с ручным вводом с консоли. Если нужен диалог с пользователем, то имхо, нужно делать GUI!
Удел консольных приложений работа с ком.строкой, перенаправленный ввод, вывод. и т.д.
Вобщем если вам нужен ручной консольный ввод, то вам это не подойдет.
Сорри что побеспокоил.

Цитата(shaukote @  8.4.2013,  21:52 Найти цитируемый пост)
) Как вообще может работать вариант Alexeis?   

Код

int main()
{
    wstring text, word;
    getline(wcin, text);
    wcout << text << endl;

    wistringstream input(text);
    while (input >> word)
        wcout << word << endl;
}


В студии этот код работает только в неконсольном приложении, и фактически выводит в кодировке cp866
перенаправьте в файл, увидите. (никакой мультиязычности т.е. нет)
В нормальном юникодном приложении этот код выдает следующее:
Код

Абвгд
€¡¢£¤
€¡¢£¤

Да, и кстати , вариант с _setmode нужно тоже проверять на настоящем юникодном приложении..  smile 

Автор: shaukote 8.4.2013, 23:42
Цитата(volatile @  8.4.2013,  23:38 Найти цитируемый пост)
Нет на ввод он действительно в этом виде не работает .

А Вы вроде бы говорили, что можно это как-то "обойти"?..


Цитата(volatile @  8.4.2013,  23:38 Найти цитируемый пост)
 вариант с _setmode нужно тоже проверять на настоящем юникодном приложении..

"Настоящем юникодном приложении"?.. Что Вы под этим подразумеваете? 

Автор: Alexeis 8.4.2013, 23:52
Цитата(volatile @  9.4.2013,  00:38 Найти цитируемый пост)
В студии этот код работает только в неконсольном приложении, и фактически выводит в кодировке cp866
перенаправьте в файл, увидите. (никакой мультиязычности т.е. нет)
В нормальном юникодном приложении этот код выдает следующее:

  Подтверждаю, сегодня проверял на VS2005 . Файловый ввод/вывод UTF8 заработал только через бустовские локали, а вот UTF16 никак. Единственный рабочий способ через стандартные фасеты С++11 . Делают шаблонный фасет из wchar_t в wchar_t причем там по ходу реальное преобразование, поскольку юникодные потоки выгребают файл в буфер из char, точно также как это делают не юникодные. И если для UTF8 еще оправдана сборка символов с char-в, то для wchar_t сие преобразование выглядит как бред. Возможно, местные гуру С++ найдут в этом сакральный смысл, но мое ИМХО, что нехай ifstream рассматривает файл как char-ы и учиться извлекать из него строк как string так и wstring, а вот  wifstream должен файл рассматривать как поток wchar_t и уже их транслировать во что угодно.
  
  P.S. удивительно как в билдере консолька юникодная. Вероятно, имеет место тотальная юникодизация видимо разработчики где-то удачно накосячили раз работает.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)