O que há de “errado” com C ++ wchar_t e wstrings? Quais são algumas alternativas para personagens largos?

86

Tenho visto muitas pessoas na comunidade C ++ (particularmente ## c ++ em freenode) se ressentirem do uso de wstringse wchar_t, e seu uso na API do Windows. O que é exatamente "errado" com wchar_te wstring, e se eu quero apoiar a internacionalização, quais são algumas alternativas para personagens amplos?

Ken Li
fonte
1
Tem alguma referência para isso?
Dani
14
Talvez este tópico incrível responda a todas as suas perguntas? stackoverflow.com/questions/402283/stdwstring-vs-stdstring
MrFox
15
No Windows, você realmente não tem escolha. Suas APIs internas foram projetadas para UCS-2, o que era razoável na época, uma vez que era antes das codificações UTF-8 e UTF-16 de comprimento variável serem padronizadas. Mas agora que eles suportam o UTF-16, eles acabaram com o pior dos dois mundos.
Jamesdlin de
12
utf8everywhere.org tem uma boa discussão de razões para evitar caracteres largos.
JoeG de
5
@jamesdlin Certamente você tem uma escolha. A biblioteca nowide fornece uma maneira conveniente de converter strings apenas ao passar para as APIs. As chamadas de API com strings geralmente são de baixa frequência, portanto, a maneira razoável é converter ad-hok e ter arquivos e variáveis ​​internas em UTF-8 o tempo todo.
Pavel Radzivilovsky

Respostas:

114

O que é wchar_t?

wchar_t é definido de forma que qualquer codificação char locale possa ser convertida em uma representação wchar_t em que cada wchar_t representa exatamente um ponto de código:

O tipo wchar_t é um tipo distinto cujos valores podem representar códigos distintos para todos os membros do maior conjunto de caracteres estendidos especificado entre as localidades com suporte (22.3.1).

                                                                               - C ++ [fundamento básico] 3.9.1 / 5

Isso não requer que wchar_t seja grande o suficiente para representar qualquer caractere de todas as localidades simultaneamente. Ou seja, a codificação usada para wchar_t pode ser diferente entre os locais. O que significa que você não pode necessariamente converter uma string em wchar_t usando uma localidade e depois converter de volta para char usando outra localidade. 1

Visto que usar wchar_t como uma representação comum entre todas as localidades parece ser o uso principal de wchar_t na prática, você pode se perguntar para que serve, senão isso.

A intenção e o propósito original de wchar_t era tornar o processamento de texto simples, definindo-o de forma que ele exigisse um mapeamento um-para-um das unidades de código de uma string para os caracteres do texto, permitindo assim o uso dos mesmos algoritmos simples que são usados com strings ascii para trabalhar com outros idiomas.

Infelizmente, o texto da especificação wchar_t pressupõe um mapeamento um a um entre caracteres e pontos de código para conseguir isso. O Unicode quebra essa suposição 2 , portanto, você também não pode usar wchar_t com segurança para algoritmos de texto simples.

Isso significa que o software portátil não pode usar wchar_t como uma representação comum para texto entre localidades ou para permitir o uso de algoritmos de texto simples.

Qual é a utilidade de wchar_t hoje?

Não muito, pelo menos para código portátil. Se __STDC_ISO_10646__for definido, os valores de wchar_t representam diretamente os pontos de código Unicode com os mesmos valores em todos os locais. Isso torna seguro fazer as conversões entre locais mencionadas anteriormente. No entanto, você não pode confiar apenas nele para decidir que pode usar wchar_t dessa forma porque, embora a maioria das plataformas unix o defina, o Windows não o faz, embora o Windows use o mesmo local wchar_t em todos os locais.

O motivo pelo qual o Windows não define __STDC_ISO_10646__é porque o Windows usa UTF-16 como sua codificação wchar_t e porque UTF-16 usa pares substitutos para representar pontos de código maiores que U + FFFF, o que significa que UTF-16 não atende aos requisitos para __STDC_ISO_10646__.

Para o código específico da plataforma, wchar_t pode ser mais útil. É essencialmente necessário no Windows (por exemplo, alguns arquivos simplesmente não podem ser abertos sem o uso de nomes de arquivo wchar_t), embora o Windows seja a única plataforma onde isso é verdade até onde eu sei (então talvez possamos pensar em wchar_t como 'Windows_char_t').

Em retrospectiva, wchar_t claramente não é útil para simplificar o tratamento de texto ou como armazenamento para texto independente de localidade. O código portátil não deve tentar usá-lo para esses fins. O código não portátil pode ser útil simplesmente porque alguma API o exige.

Alternativas

A alternativa que eu gosto é usar strings C codificadas em UTF-8, mesmo em plataformas não particularmente amigáveis ​​para UTF-8.

Desta forma, pode-se escrever código portátil usando uma representação de texto comum entre plataformas, usar tipos de dados padrão para os fins pretendidos, obter o suporte da linguagem para esses tipos (por exemplo, literais de string, embora alguns truques sejam necessários para fazê-lo funcionar para alguns compiladores), alguns suporte a biblioteca padrão, suporte a depurador (mais truques podem ser necessários), etc. Com caracteres largos é geralmente mais difícil ou impossível de obter tudo isso, e você pode obter peças diferentes em plataformas diferentes.

Uma coisa que o UTF-8 não oferece é a capacidade de usar algoritmos de texto simples, como os possíveis com ASCII. Neste UTF-8 não é pior do que qualquer outra codificação Unicode. Na verdade, pode ser considerado melhor porque as representações de unidades de vários códigos em UTF-8 são mais comuns e, portanto, os bugs no código que lida com essas representações de largura variável de caracteres são mais prováveis ​​de serem notados e corrigidos do que se você tentar manter o UTF -32 com NFC ou NFKC.

Muitas plataformas usam UTF-8 como codificação nativa de caracteres e muitos programas não requerem nenhum processamento de texto significativo, portanto, escrever um programa internacionalizado nessas plataformas é um pouco diferente de escrever código sem considerar a internacionalização. Escrever um código mais amplamente portátil ou em outras plataformas requer a inserção de conversões nos limites de APIs que usam outras codificações.

Outra alternativa usada por alguns softwares é escolher uma representação de plataforma cruzada, como matrizes curtas não assinadas contendo dados UTF-16, e então fornecer todo o suporte da biblioteca e simplesmente viver com os custos de suporte de linguagem, etc.

C ++ 11 adiciona novos tipos de caracteres largos como alternativas para wchar_t, char16_t e char32_t com recursos de linguagem / biblioteca associados. Na verdade, não há garantia de que sejam UTF-16 e UTF-32, mas não imagino que nenhuma implementação principal use outra coisa. C ++ 11 também melhora o suporte UTF-8, por exemplo, com literais de string UTF-8, então não será necessário enganar o VC ++ para produzir strings codificadas em UTF-8 (embora eu possa continuar a fazer isso em vez de usar o u8prefixo) .

Alternativas a evitar

TCHAR: TCHAR é para migrar programas antigos do Windows que assumem codificações legadas de char para wchar_t e é melhor esquecê-lo, a menos que seu programa tenha sido escrito em algum milênio anterior. Não é portátil e é inerentemente inespecífico sobre sua codificação e até mesmo seu tipo de dados, tornando-o inutilizável com qualquer API não baseada em TCHAR. Como seu propósito é a migração para wchar_t, o que vimos acima não é uma boa ideia, não há nenhum valor em usar o TCHAR.


1. Os caracteres que são representáveis ​​em cadeias wchar_t, mas que não são suportados em qualquer local, não precisam ser representados com um único valor wchar_t. Isso significa que wchar_t poderia usar uma codificação de largura variável para certos caracteres, outra violação clara da intenção de wchar_t. Embora seja discutível que um caractere sendo representado por wchar_t é suficiente para dizer que a localidade 'suporta' esse caractere, caso em que codificações de largura variável não são legais e o uso de UTF-16 pelo Windows não está em conformidade.

2. O Unicode permite que muitos caracteres sejam representados com vários pontos de código, o que cria os mesmos problemas para algoritmos de texto simples como codificações de largura variável. Mesmo que se mantenha estritamente uma normalização composta, alguns caracteres ainda requerem vários pontos de código. Veja: http://www.unicode.org/standard/where/

bames 53
fonte
3
Adição: utf8everywhere.org recomenda o uso de UTF-8 no Windows, e Boost.Nowide está agendado para revisão formal.
Yakov Galka
2
A melhor coisa, claro, é usar C # ou VB.Net no Windows :) Ou simplesmente C / Win32 antigo. Mas se você deve usar C ++, então TCHAR é o melhor caminho a seguir. Qual padrão é "wchar_t" no MSVS2005 e superior. IMHO ...
paulsm4
4
@BrendanMcK: Claro, o código que usa a API Win32 no Windows e outras APIs em outros sistemas não existe. Direito? O problema com a abordagem da Microsoft ("use wchar internamente em qualquer lugar em seu aplicativo") é que afeta até mesmo o código que não faz interface direta com o sistema e pode ser portátil.
Yakov Galka
4
O problema é que você precisa usar funções específicas do Windows porque a decisão da Microsoft de não oferecer suporte a UTF-8 como uma página de código ANSI "quebra" a Biblioteca C (++) Padrão. Por exemplo, você não pode usar fopenum arquivo cujo nome contenha caracteres não ANSI.
dan04
11
@ dan04 Sim, você não pode usar a biblioteca padrão no Windows, mas pode criar uma interface portátil que envolve a biblioteca padrão em outras plataformas e converte de UTF-8 para wchar_t diretamente antes de usar as funções W do Win32.
bames53
20

Não há nada "errado" com wchar_t. O problema é que, nos dias do NT 3.x, a Microsoft decidiu que o Unicode era bom (é) e implementou o Unicode como caracteres wchar_t de 16 bits. Portanto, a maior parte da literatura da Microsoft de meados dos anos 90 equivalia a Unicode == utf16 == wchar_t.

O que, infelizmente, não é o caso. "Caracteres largos" não são necessariamente 2 bytes, em todas as plataformas, em todas as circunstâncias.

Este é um dos melhores primers em "Unicode" (independente desta questão, independente de C ++) que eu já vi: Eu recomendo fortemente :

E eu honestamente acredito que a melhor maneira de lidar com "ASCII de 8 bits" vs "caracteres amplos do Win32" vs "wchar_t-in-general" é simplesmente aceitar que "Windows é diferente" ... e codificar de acordo.

NA MINHA HUMILDE OPINIÃO...

PS:

Eu concordo totalmente com jamesdlin acima:

No Windows, você realmente não tem escolha. Suas APIs internas foram projetadas para UCS-2, o que era razoável na época, uma vez que era antes das codificações UTF-8 e UTF-16 de comprimento variável serem padronizadas. Mas agora que eles suportam o UTF-16, eles acabaram com o pior dos dois mundos.

paulsm4
fonte