为什么C ++中的宽文件流默认会缩小写入数据?

时间:2009-10-02 12:39:56

标签: c++ file unicode wofstream

老实说,我在C ++标准库中没有得到以下设计决定。将宽字符写入文件时,wofstream会将wchar_t转换为char个字符:

#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!
}

我知道这与标准codecvt有关。 Boost中的codecvt utf8。另外,codecvt utf16 standard codecvt unicode streams。问题是为什么 {{1}}转换宽字符?为什么不按原样写下角色呢!

另外,我们是否会使用C ++ 0x获得真正的{{1}}或者我在这里遗漏了什么?

5 个答案:

答案 0 :(得分:13)

第一个问题的一个非常局部的答案:文件 是一个字节序列,所以在处理wchar_t时,至少一些转换必须在wchar_tchar之间发生。 “智能地”进行这种转换需要知道字符编码,因此这就是为什么允许这种转换依赖于语言环境,因为在流的语言环境中使用了一个方面。

然后,问题是如何在标准所要求的唯一区域设置中进行转换:“经典”转换。对此没有“正确”的答案,因此标准对此非常模糊。我从你的问题中了解到你认为在wchar_t []和char []之间盲目地进行(或memcpy() - ing)会是一个好方法。这不是不合理的,事实上在某些实现中是(或至少是)完成的。

另一个POV是,因为codecvt是一个语言环境方面,所以有理由期望使用“locale的编码”进行转换(我在这里手写,因为概念非常模糊)。例如,可以预期土耳其语语言环境使用ISO-8859-9或日语使用Shift JIS。通过相似性,“经典”语言环境将转换为此“语言环境的编码”。显然,微软选择简单修剪(如果我们假设wchar_t代表UTF-16并且我们保持基本的多语言平面,则导致IS-8859-1),而我知道的Linux实现决定坚持ASCII。

关于你的第二个问题:

  

另外,我们是否会使用C ++ 0x获得真正的unicode流,或者我在这里遗漏了什么?

在n2857的[locale.codecvt]部分(我手头的最新C ++ 0x草案)中,可以读到:

  

专门化codecvt<char16_t, char, mbstate_t>在UTF-16和UTF-8编码方案之间进行转换,专门化codecvt <char32_t, char, mbstate_t>在UTF-32和UTF-8编码方案之间进行转换。 codecvt<wchar_t,char,mbstate_t>在本地字符集之间转换为窄字符和宽字符。

在[locale.stdcvt]部分,我们找到:

  

对于方面codecvt_utf8:    - 方面应在程序内转换UTF-8多字节序列和UCS2或UCS4(取决于Elem的大小)。   [...]

     

对于方面codecvt_utf16:    - 方面应在程序内转换UTF-16多字节序列和UCS2或UCS4(取决于Elem的大小)。   [...]

     

对于方面codecvt_utf8_utf16:    - 方面应在程序内转换UTF-8多字节序列和UTF-16(一个或两个16位代码)。

所以我猜这意味着“是”,但你必须更准确地确定“真正的unicode流”的意思。

答案 1 :(得分:7)

C ++用于charsets的模型继承自C,因此可以追溯到至少1989年。

两个要点:

  • IO是以char为单位完成的。
  • 区域设置的工作是确定如何将字符串序列化
  • 默认语言环境(名为“C”)非常小(我不记得标准中的约束,这里它只能处理7位ASCII作为窄字符和宽字符集)。
  • 有一个环境确定的区域设置名为“”

为了得到任何东西,你必须设置语言环境。

如果我使用简单程序

#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";
    }
}

使用环境语言环境并将代码0x00FF的宽字符输出到文件。如果我要求使用“C”语言环境,我会得到

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

语言环境无法处理宽字符,并且在IO失败时我们会收到问题通知。如果我运行询问UTF-8语言环境,我会得到

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

(od -t x1只是转储以十六进制表示的文件),正是我对UTF-8编码文件的期望。

答案 2 :(得分:3)

我不知道wofstream。但是C ++ 0x将包括保证宽度和签名(unsigned)的新distict字符类型(char16_t,char32_t),它可以便携地用于UTF-8,UTF-16和UTF-32。此外,还会有新的字符串文字(例如,对于UTF-16编码的字符串文字,你是“Hello!”)

查看最新的C++0x draft (N2960)

答案 3 :(得分:2)

对于你的第一个问题,这是我的猜测。

IOStreams库是在几个有关编码的前提下构建的。例如,为了在Unicode和其他不常见的编码之间进行转换,假设它是。

  • 在程序中,您应该使用(固定宽度)宽字符编码。
  • 只有外部存储应使用(可变宽度)多字节编码。

我认为这就是std :: codecvt存在两个模板特化的原因。一个在char类型之间映射(可能你只是使用ASCII)和另一个映射wchar_t(程序内部)和char(外部设备)之间的映射。因此,无论何时需要执行到多字节编码的转换,都应该逐字节进行。请注意,当您从/向多字节编码读取/写入每个字节时,您可以编写一个处理编码状态的构面。

以这种方式思考C ++标准的行为是可以理解的。毕竟,您使用的是宽字符ASCII编码(假设这是您平台上的默认设置,并且您没有切换区域设置)字符串。 “自然”转换是将每个宽字符ASCII字符转换为普通(在本例中为一个字符)ASCII字符。 (转换存在并且很简单。)

顺便说一句,我不确定你是否知道,但你可以通过创建一个返回 noconv 转换的方面来避免这种情况。然后,您的文件将包含宽字符。

答案 4 :(得分:2)

检查一下: Class basic_filebuf

您可以使用pubsetbuf通过设置 wide char缓冲区来更改默认行为。 一旦你这样做,输出将是wchar_t而不是char。

换句话说,您将拥有:

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)