2009-10-02 7 views
18

Onestamente, non ottengo la seguente decisione di progettazione nella libreria standard C++. Durante la scrittura di caratteri estesi in un file, il wofstream converte wchar_t in char caratteri:Perché il flusso di file esteso in C++ contiene dati scritti per impostazione predefinita?

#include <fstream> 
#include <string> 

int main() 
{ 
    using namespace std; 

    wstring someString = L"Hello StackOverflow!"; 
    wofstream file(L"Test.txt"); 

    file << someString; // the output file will consist of ASCII characters! 
} 

Sono consapevole che questo ha a che fare con lo standard codecvt. C'è codecvt per utf8 in Boost. Inoltre, c'è un codecvt per utf16 entro il Martin York here on SO. La domanda è perché il standard codecvt converte caratteri wide? perché non scrivere i personaggi così come sono!

Inoltre, otterremo il vero unicode streams con C++ 0x o mi manca qualcosa qui?

+3

Buona domanda. Spero che tu possa trovare una risposta. Personalmente mi sto appoggiando alla teoria "IOStreams è solo una biblioteca mal progettata";) Probabilmente non aiuta che Unicode non fosse esattamente ben stabilito quando la libreria è stata progettata. Potrebbero aver pensato che la serializzazione da/verso caratteri semplici fosse l'approccio più portabile. – jalf

+0

@jalf Grazie. Non sono molto abile con i flussi ma questa domanda mi infastidisce molto: D – AraK

risposta

7

Il modello utilizzato da C++ per set di caratteri è ereditato da C, e così risale almeno al 1989.

due punti principali:

  • IO viene fatto in termini di char.
  • è il lavoro delle impostazioni internazionali per determinare come vengono serializzati caratteri grandi
  • le impostazioni locali predefinite (denominate "C") sono molto minime (non ricordo i vincoli dello standard, qui è in grado di gestire solo ASCII a 7 bit come set di caratteri stretto e ampio).
  • v'è un locale ambiente determinato dal nome ""

Quindi, per ottenere qualcosa, è necessario impostare l'impostazione internazionale.

Se uso il semplice programma

#include <locale> 
#include <fstream> 
#include <ostream> 
#include <iostream> 

int main() 
{ 
    wchar_t c = 0x00FF; 
    std::locale::global(std::locale("")); 
    std::wofstream os("test.dat"); 
    os << c << std::endl; 
    if (!os) { 
     std::cout << "Output failed\n"; 
    } 
} 

che utilizzano il locale ambiente e in uscita l'ampio carattere del codice 0x00FF in un file. Se chiedo di utilizzare il locale "C", ottengo

$ env LC_ALL=C ./a.out 
Output failed 

il locale è stato in grado di gestire il carattere ampio e otteniamo notificato il problema come l'IO non è riuscita. Se corro chiedere a un locale UTF-8, ottengo

$ env LC_ALL=en_US.utf8 ./a.out 
$ od -t x1 test.dat 
0000000 c3 bf 0a 
0000003 

(od -t x1 solo il dump del file rappresentato in esadecimale), esattamente quello che mi aspetto per un file codificato UTF-8.

+0

Scommetto che l'output è fallito perché si aspettava un altro personaggio. E il secondo non è quello che mi aspetterei. a meno che non ignori completamente i bit alti del wchar_t. Cosa succede se produci c = 0xABCD; Codifica il CD in UTF-8 e ignora AB? o è tutto codificato. Cosa succede quando il carattere UTF-8 è lungo tre byte? –

+0

Inoltre ottengo risultati diversi. C: (ff 0a) en_US.utf8: (std :: runtime_error [locale :: facet :: _ S_create_c_locale nome non valido]) –

+0

Non capisco perché C3 BF non sia la codifica di 0x00FF in cui ti aspetti. E per 0xABCD dà EA AF 8D che è quello che mi aspettavo. Quello che non mi aspettavo è che consentisse 0xDCBA (è un punto di codice surrogato e non valido) e altri punti di codice non validi. – AProgrammer

13

Una risposta molto parziale per la prima domanda: Un file è una sequenza di byte modo, quando si tratta di wchar_t 's, almeno alcuni conversione tra wchar_t e char deve avvenire. Rendere questa conversione "intelligente" richiede la conoscenza delle codifiche dei caratteri, quindi questo è il motivo per cui questa conversione può essere dipendente dalla locale, in virtù dell'uso di un facet nelle impostazioni locali del flusso.

Quindi, la domanda è come tale conversione dovrebbe essere effettuata nell'unica locale richiesta dallo standard: quella "classica". Non c'è una risposta "giusta" per questo, e lo standard è quindi molto vago a riguardo. Capisco dalla tua domanda che pensi che gettare ciecamente (o memcpy() - ing) tra wchar_t [] e char [] sarebbe stato un buon modo. Questo non è irragionevole, ed è in effetti ciò che è (o almeno è stato) fatto in alcune implementazioni.

Un altro POV sarebbe che, dal momento che un codice è un aspetto locale, è ragionevole aspettarsi che la conversione venga effettuata utilizzando la "codifica del locale" (qui ho la mano libera, poiché il concetto è piuttosto sfocato). Ad esempio, ci si aspetterebbe che un locale turco utilizzi ISO-8859-9 o un giapponese per usare Shift JIS. Per somiglianza, il locale "classico" si converte in questa "codifica del locale". Apparentemente, Microsoft ha preferito semplicemente tagliare (che porta a IS-8859-1 se supponiamo che wchar_t rappresenti UTF-16 e che rimaniamo nel piano multilingue di base), mentre l'implementazione Linux che conosco ha deciso di passare all'ASCII.

riguarda la seconda domanda:

Inoltre, siamo andando ottenere flussi reale Unicode con C++ 0x o mi manca qualcosa qui?

Nella sezione [locale.codecvt] del n2857 (l'ultima bozza C++ 0x ho a portata di mano), si legge:

La specializzazione codecvt<char16_t, char, mbstate_t> converte tra l'UTF-16 e Schemi di codifica UTF-8 e la specializzazione codecvt <char32_t, char, mbstate_t> converte tra gli schemi di codifica UTF-32 e UTF-8. codecvt<wchar_t,char,mbstate_t> converte tra i set di caratteri nativi per caratteri stretti e larghi.

Nel [locale.stdcvt] sezione, troviamo:

Per la sfaccettatura codecvt_utf8: - L'aspetto è la conversione tra UTF-8 sequenze multibyte e UCS2 o UCS4 (a seconda delle dimensioni delle Elem) all'interno del programma. [...]

Per la sfaccettatura codecvt_utf16: - L'aspetto è la conversione tra UTF-16 sequenze multibyte e UCS2 o UCS4 (a seconda delle dimensioni delle Elem) all'interno del programma. [...]

Per la sfaccettatura codecvt_utf8_utf16: - L'aspetto è la conversione tra UTF-8 sequenze multibyte e UTF-16 (uno o due codici a 16 bit) all'interno del programma.

Quindi immagino che questo significhi "sì", ma per essere sicuri, dovresti essere più preciso su cosa intendi per "flussi di unicode reali".

+0

@ Éric Grazie. Finalmente stiamo ottenendo veri stream Unicode :) – AraK

+0

@ Éric intendevo che i flussi sono a conoscenza di Unicode, come lo è C++ 0x. Sto ancora cercando una risposta logica alla domanda principale. – AraK

3

Non so di wofstream. Ma C++ 0x includerà nuovi tipi di caratteri distict (char16_t, char32_t) di larghezza garantita e signness (unsigned) che possono essere utilizzati in modo portabile per UTF-8, UTF-16 e UTF-32. Inoltre, ci saranno nuove stringhe letterali (u "Hello!" Per un letterale stringa codificato UTF-16, ad esempio)

Controlla il più recente C++0x draft (N2960).

2

Per la tua prima domanda, questa è la mia ipotesi.

La libreria IOStreams è stata costruita in base a un paio di premesse relative alle codifiche.Per la conversione tra Unicode e altre codifiche non usuali, ad esempio, si presume che.

  • All'interno del programma, è necessario utilizzare una codifica a caratteri ampi (larghezza fissa).
  • Solo la memoria esterna deve utilizzare codifiche multibyte (larghezza variabile).

Credo che questo sia il motivo dell'esistenza delle due specializzazioni di modello di std :: codecvt. Uno che mappa tra tipi di caratteri (forse stai semplicemente lavorando con ASCII) e un altro che esegue il mapping tra wchar_t (interno al tuo programma) e char (dispositivi esterni). Quindi ogni volta che è necessario eseguire una conversione in una codifica multibyte, è necessario farlo byte per byte. Si noti che è possibile scrivere un facet che gestisce lo stato di codifica quando si legge/scrive ogni byte da/alla codifica multibyte.

Pensando in questo modo è comprensibile il comportamento dello standard C++. Dopo tutto, stai usando le stringhe ASCII a caratteri ampi (supponendo che questo sia il default sulla tua piattaforma e tu non abbia cambiato locale). La conversione "naturale" sarebbe quella di convertire ciascun carattere ASCII di carattere ampio in un carattere ASCII ordinario (in questo caso, un carattere). (La conversione esiste ed è semplice.)

A proposito, non sono sicuro se lo sai, ma puoi evitarlo creando un facet che restituisce noconv per le conversioni. Quindi, avresti il ​​tuo file con caratteri ampi.

+0

I tuoi locali probabilmente non saranno validi. UTF-16 è multibyte. La maggior parte delle persone considera UTF-32 uno spreco per i dati dei personaggi (io no), quindi finiremo per usare UTF-16 e avere tutto il codice in più per gestire il caso d'angolo speciale delle coppie di surrogati. –

+0

@Martin: UTF-8 e UTF-16 sono tutti multibyte. Non ho detto che erano a larghezza fissa. Non capisco esattamente cosa stai dicendo. –

3

Check this out: Class basic_filebuf

È possibile modificare il comportamento predefinito impostando un buffer char vasta, utilizzando pubsetbuf. Una volta fatto, l'output sarà wchar_t e non char.

In altre parole per il vostro esempio si avrà:

wofstream file(L"Test.txt", ios_base::binary); //binary is important to set! 
wchar_t buffer[128]; 
file.rdbuf()->pubsetbuf(buffer, 128); 
file.put(0xFEFF); //this is the BOM flag, UTF16 needs this, but mirosoft's UNICODE doesn't, so you can skip this line, if any. 
file << someString; // the output file will consist of unicode characters! without the call to pubsetbuf, the out file will be ANSI (current regional settings) 
Problemi correlati