Pull to refresh

Comments 43

со строками в Линуксе нужно работать в кодировке UTF-8 и в типе std::string, а в Windows строки должны быть в Юникоде (тип std::wstring). Почему? Потому что это by design операционных систем. Хранить строки в std::wstring в Линуксе крайне накладно, поскольку один символ wchar_t занимает 4 байта (в Windows — 2 байта),
Разве это бидезигн? Можно и там и там как угодно работать, как сами ниже говорите — в линуксе строки можно же и в wchar_t хранить. В винде sdk/api он двухбайтовый, потому что там исторически сложилось так с древних времён ещё, хотя это неправильно. И там в этих двух байтах просто символы UTF-16 хранятся (либо если UTF-16 символ окажется четырёхбайтовым, то в двух wchar_t ). UTF-8 здесь и ниже по тексту — это же просто вопрос кодирования юникода, а выбор строки, чаров итд — вопрос хранения самих символов. Вы намеренно смешиваете понятия (ровно как и понятия «в винде строки в юникоде» и «в линуксе строки в utf-8»), либо я не понимаю чего-то тут?
Хранить можно в чем угодно, весь вопрос только в удобстве. Можно и в Windows строки хранить в utf8, только каждый раз при вызове SetWindowText вам придется делать конвертацию из utf8.
Мне показалось, вы рассказываете о том, как писать переносимый код. Какой SetWindowText в Линуксе?
Пример с SetWindowText немного утрирован. Но он не меняет сути. На самом деле в такой разработке может всегда понадобиться вызвать функцию WinAPI, в которую нужно передать строку. Статья называется «кроссплатформенная работа со строками», а не то как писать «100% кроссплатформенное приложение».
Статья называется «кроссплатформенная работа со строками», а не то как писать «100% кроссплатформенное приложение».

Такие кроссплатформенный строки никому не нужны. Поскольку кроссплатформенно с ними ничего делать нельзя.

Посмотрите ради интереса, как эта проблема решена в boost::filesystem::path (внутри хранит все в utf-8).
Внутри себя boost::filesystem::path хранит в std::string. В линуксе с utf-8 проблем не будет, но если вы в Windows скормите путь utf-8 (содержащий символы национального алфавита) в boost::filesystem::path, то ничего хорошего из этого не выйдет.
В манифесте www.utf8everywhere.org рекомендуют везде использовать только UTF8 и ни в каком случае не использовать UTF16 потому что большинство его используют неправильно.

Я честно говоря не согласен с авторами манифеста потому что у меня и нет желания поддерживать языки не входящие в UTF16/UCS2. Мне вполне их достаточно для моего приложения (What languages can not be displayed in the UCS2 encoding?). Я не хочу в разы усложнять себе жизнь ради поддержки этих символов.
wchar_t и там, и сям может быть разного размера, зависит от версии Юникода, поддерживаемого компилятором. Просто в Линуксе автору подвернулся компилятор поновее, чем в Windows.
sizeof(wchar_t) равен 2 в VC2010. Разве в компиляторах от Microsoft wchar_t может быть другого размера?
Вообще в компиляторах может. Конкретно про Microsoft не скажу.
Это понятно, строго говоря, стандарт порицает думать о том, какого размера wchar_t, и он может быть любого. С другой стороны, емнип, он обязан вмещать все символы системозависимой кодировки, чего в windows не происходит (раз символы там могут представляться более, чем двухбайтово). В вин от компилятора не зависит, конечно же, там 2 байта, и будет так, видимо, в обозримом будущем.
Дело в том, что в виндовсе символы не могут быть более чем двухбайтовыми. Там реализовано подмножество Unicode UCS-2 в котором можно кодировать только символы из BMP.

По теме — ящитаю в никсах с текстом надо работать в UTF-32, хранить в UTF-8, а в виндовсе — и обрабатывать и хранить UTF-16 (UCS-2), поскольку это несёт минимум хлопот, позволяет обрабатывать строки быстро (символы фиксированной длинны позволяют проще реализовать случайный доступ к конкретному символу) и хранить их эффективно с минимальными хлопотами.

Использование UCS-2 в винде накладывает некоторые ограничения — но, как мне кажется, эти ограничения имеют скорее теоретический характер.
Ну, т.е. символы то могут быть какими угодно, но система поддерживает именно UCS-2.
Там реализована UCS-2 потому, что на момент реализации никакой UTF-16 ещё не было фактически. В Java, кстати, совершенно аналогично всё. Тем не менее, очевидно, символы выше в винде не табу. Как так «не могут быть»? Могут, конечно. Ну и как их хранить, если wchat_t там 2-байтовый? Именно, что такие символы реализуются двумя wchat_t. Поправьте меня — это не так?
На счет причин реализации UCS-2 я не очень-то в курсе, если честно. Было бы интерестно почитать.
А по поводу не-BMP символов, ситуация следующая — вы можете хранить их в виндовых строках, однако, насколько мне известно, системные средства подсчета количества символов в строке (wcslen и иже с ним) будут давать неправельные результаты.
Обычно для кроссплатформенной работы со строками на C++ используют какой-нибудь фреймворк. Например — Qt.
Во-первых, помимо Qt я что не припомню других удачных кроссплатформенных фреймворков для C++. :)
Во-вторых, если бы речь шла о Qt, по статья бы состояла из одного предложения «Для кроссплатформенной работы со строками на C++ используйте класс QString от Qt.»
Обычно для всего этого используют boost, а Qt используют тогда, когда он и так в проекте. Специально тянуть Qt не очень умная затея.
В boost есть кроссплатформенные, юникодные строки? O_O
Это, к сожалению, не решает основной проблемы использования разных контейнеров под разными операционками и следующих из этого преобразований, ifdef'ов и прочих танцев с бубнами.
Не понял Вашей мысли, что мешает использовать std::string и UTF-8 везде?
Под windows придется конвертировать в UCS-2 вообще везде. Плюс, как в таком случае делать операции вида «заменить три последних символа»? Все с помощью boost.locale? :)
Что значит «вообще везде»? У кода есть ядро, есть внешние выходы. Вот на внешнхи выходах и конвертируем, в чем проблема-то? Если не создавать себе лишних проблем, то они и не появляются.
Плюс, как в таком случае делать операции вида «заменить три последних символа»? Все с помощью boost.locale?

Если Вас устраивает делать это с помощью QString, почему бы не использовать boost.locale, тогда?
Для QString и ICU это будут методы QString и UnicodeString. В случае использования boost.locale это будут внешние методы над строками, что сильно затруднит чтение кода.

stl'овские алгоритмы над такой строкой прменять будет нельзя, потому что она итерируется не посимвольно. Вообще сам факт того, что строки будут итерироваться не посимвольно открывает очень широкие возможности для внесения багов в будующем — при поддержке, модификации, найме еще программистов на проект :(.

Для Windows при минимальном использовании WinAPI/COM/whatever таких «внешних выходов» будет дофига и больше. ИМХО, это будет архитектурное решение, порождающее избыточное количество кода, который не имеет отношения к решаемой задачи а служит клеем между абстракциями. Как показывает практика, такой код труднее читать и поддерживать — тривиально больше кода.

Хотя, конечно, если оценивать умность затеи — то тут я с вами соглашусь, boost позволяет такой затейливый код писать — что без умного разработчика не разберешся :).
Для QString и ICU это будут методы QString и UnicodeString. В случае использования boost.locale это будут внешние методы над строками, что сильно затруднит чтение кода.

Внешние по отношению к чему? семантически они являются дополняющими, а не внешними, а, следовательно, в терминах ООП являются частью сущности. В чем сложность использования функции вместо метода мне вообще не ясно. Ну да ладно, тут нет никакой математической правды, о которой спорить. Вы можете упираться про неудобство до посинения и я всё равно ничего не смогу сказать объективного в ответ.
Для Windows при минимальном использовании WinAPI/COM/whatever таких «внешних выходов» будет дофига и больше. ИМХО, это будет архитектурное решение, порождающее избыточное количество кода, который не имеет отношения к решаемой задачи а служит клеем между абстракциями. Как показывает практика, такой код труднее читать и поддерживать — тривиально больше кода.

Как QString решит данную проблему?
У QString есть волшебные методы toUtf8, toUtf16, toStdWstring и так далее — которые позволяют ему безболезненно общаться с внешним миром.
В boost.locale они тоже есть.
Значит используем boost.locale и радуемся, что строки итерируются не посимвольно, а побайтово :).
P.S про которые алгоритмы stl вы говорите, ксттаи. Почти все алгоритмы могут принимать comparator, а следовательно можно свободно использовать boost.locale с stl
Выше уже предупредили об неточности в строке «Windows строки должны быть в Юникоде». По-моему стоит исправить статью, чтобы не распространялось.

А вообще к выбору кодировки стоит подходить осторожно. Никаких проблем в линуксе, utf-8 обеспечит работу программы с чем угодно. Проблемы начнутся, когда вы захотите применить utf-8 к сломанной кодировке utf-16 в представлении windows. Дело в том, что локализованные версии windows периодически мешают кодировки в именах файлов. В результате получаются пути вида www.graphicall.org/ftp/ideasman42/bad_utf8.txt (кодировка utf-8, открывать в редакторе типа SciTE, которые умеют работать с битыми кодировками). Я эту блядскую строку даже не могу на хабр добавить, поскольку здравомыслящие браузеры при копировании и вставке меняют битый символ на символ подстановки �. На самом деле за этим символом в данное случае скрывается символ с кодом 0xE9 (é) из однобайтовой кодировки Windows-1252. Такие строки нельзя преобразовать обратно в utf-16 так, чтобы получилась исходная битая строка. В связи с этим, с подобными файлами не смогут работать все программы в линуксе, а также программы, написанные по статье.
Извиняюсь за излишнюю эмоциональность. О некоторых аспектах одной операционной системы (с) просто не могу сказать по-другому.
Мне кажется, что когда речь идет о представлении строк «Юникод в Windows», то все понимают что речь идет об UTF-16LE. Да, в Windows свой особый способ хранения строк, может и не совсем удачный. Но и в Линуксе тоже не все гладко. Например, преобразование строки utf-8 в нижний регистр просто так не сделаешь. Да и другие ОС не сразу пришли к utf-8 (во FreeBSD, к примеру, полноценная поддержка utf-8 появилась относительно не так давно).
Обратимое преобразование регистров невозможно ни с одной кодировкой. Так получается, например, из-за сигмы Σ, которая является заглавным представлением одновременно для ς и σ. И обратный пример — эсцет ß в верхнем регистре по правилам немецкого языка внезапно превращается в SS (несмотря на существование ẞ).
С чего бы вдруг? Во множестве языков нет регистро-зависимых преобразований, типа уже описанной Вами SS. Да и преобразование происходит верно как в icu, так и в boost.locale его использующим.
Я не знаток немецкого языка, но во всех примерах есть пример с SS, но я впервые у Вас увидел, что у ß есть односимвольная версия верхнего регистра. Вы не ошибаетесь случаем?
А если еще про мак вспомним и его NSString / CFStringRef? Или всяких там плагинов под кроссплатформенный пакет?

Бывает, что конертация строк, путей, и прочего — pain in ass еще тот.

Вот к примеру частенько приходится тягать с собой гору всяких platform dependent traits в виде UTF16Char, UTF8Char и прочих, и в нужных местах переконвертировать системозависимым способом между std::[w]string, UTF{16|8}Char*, char*, wchar_t* и пр.

Оно конечно не так чтобы сложно, по надцатому-то разу, но ведь еще и изобретать приходится вот такое

public class MyKewlInputStream(const std::wstring&) 
    defined by system-specific implementation 

что изрядно доставляет.

И не сказать чтобы такое счастье пишется один раз в пятилетку — большие старые пакеты предоставляют свои строки, свои стримы, свои конвертеры, и пр. и пр., которые как-то надо оборачивать. И из этих оберток — еще и системоспецифичные хэндлы, форки и дескрипторы торчат, как иголки из подушечки для булавок.

К примеру — адобовые PMString / WideString, PMStream.

Оборачивать приходится просто для того, чтобы не писать каждый раз нечто вроде такого

    WideString wsFileName(fileName.c_str());
    PMString pmFileName = PMString(wsFileName);

    IDFile idFileName;
    FileUtils::PMStringToIDFile(pmFileName, idFileName);
    InterfacePtr<IPMStream> stream(StreamUtil::CreateFileStreamWriteLazy(
                                                      idFileName, kOpenOut | kOpenTrunc));

    // Наконец-то у нас что-то system specific InDesign API и можно с этим работать


или такого (крестится)

#ifdef WINDOWS
    // клятый фотошоп нам подсунул под виндой UTF16Char*
    uint16* result = fSpec->mReference;
    numUtf16 = (uint32)wcslen((const wchar_t*)result);
    // для остроты ощущений это все еще в Java сунем
    jstring filePath = env->NewString(result, numUtf16);
    ...
#else
    // а под маком Fork aka FSIORefNum, добро пожаловать в отладчик
    CFStringRef path = FSRefToCFString(&fSpec->mReference);
    uint32 numUtf16 = (uint32)CFStringGetLength(path);
    UniChar* result = nil;
    if(numUtf16 > 0) {
        result = new UniChar[numUtf16+1]; 
	CFRange rangeToCopy; 
        rangeToCopy.location = 0; 
        rangeToCopy.length = numUtf16;
	CFStringGetCharacters(path, rangeToCopy, result);

        // для остроты ощущений это все еще в Java сунем
        jstring filePath = env->NewString(result, numUtf16);
        ...
    }
    CFRelease(path);
#endif


Или вспомним мозилловые nsString / nsCString и конертации туда-сюда, NS_CStringToUTF16 и NS_LITERAL, NS_UTF16ToCString и прочие. А их же SOA и do_GetService()?

nsresult GetAsText(
    const nsACString &aCharset, 
    const char* aData, 
    PRUint32 dataLength, 
    nsAString& aResult)
{
    nsresult rv;
    nsCAutoString charsetGuess = aCharset;

    nsCAutoString charset;
    nsCOMPtr<nsICharsetAlias> alias = do_GetService(NS_CHARSETALIAS_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    rv = alias->GetPreferred(charsetGuess, charset);
    NS_ENSURE_SUCCESS(rv, rv);
    ...
}
(обратите внимание — в 10-строчнике мы видим 3 разных ns*String и const char* до кучи)

Как только склеиваем ежа с ужом — тут и начинаются три метра колючей проволоки.

Не Qt единым, что называется ;-)
Мне не нравится, когда текст в utf8-кодировке хранится в std::string в силу возникающей путаницы.
Я обычно использую для этого строковый класс, не приводящийся к std::string.

typedef char utf8_char;
class utf8_char_traits: public std::char_traits<utf8_char> {};
typedef std::basic_string<utf8_char, utf8_char_traits> utf8_string;
Один глупый вопрос — а почему бы не использовать UnicodeString из icu, раз уж эта библиотека применена? У меня в проекте (который очень сильно завязан на работу со строками) именно этот класс в основном и используется. Он использует какую-то UTF-16 кодировку. Из приятных особенностей — хранение коротких строк прямо в классе, copy-on-write со счетчиком ссылкок.
Мне не нравится, когда весь проект завязан на определенную библиотеку. Если завтра по каким-либо причинам придется отказаться от icu, то придется из всего проекта выкидывать UnicodeString. А так — написал пару методов, в которых используется icu и нет проблем.
Мне не нравится, когда весь проект завязан на определенную библиотеку. Если завтра по каким-либо причинам придется отказаться от icu...
А если послезавтра по каким-либо причинам придется отказаться от stl, что делать будете?
Определить свой тип данных.
Sign up to leave a comment.

Articles