【问题标题】:Why std::basic_fstream<unsigned char> won't work?为什么 std::basic_fstream<unsigned char> 不起作用?
【发布时间】:2020-11-17 22:47:05
【问题描述】:

尝试编译此代码时:

std::fstream file("file.name", std::ios::out | std::ios::binary);
uint8_t buf[BUFSIZE];
//Fill the buffer, etc...
file.write(buf, BUFSIZE);

编译器会在调用write() 时警告我从unsigned char 到char 的哦不那么健康的转换。由于std::fstream 实际上只是std::basic_fstream&lt;char&gt; 的typedef,人们可能会认为使用std::basic_fstream&lt;uint8_t&gt; 可以让他们在没有警告的情况下编译上面的代码,因为write() 需要模板类型的指针。

这当然可行,但又出现了另一个问题。即使这段代码编译得很好:

std::basic_fstream<uint8_t> file("file.name", std::ios::out | std::ios::binary);
uint8_t buf[BUFSIZE];
//Fill the buffer, etc...
file.write(buf, BUFSIZE);

现在调用write() 时会失败,即使之前的版本正在运行(忽略编译器警告)。我花了一段时间来确定标准 C++ 库代码中异常是从哪里引发的,但我仍然不太明白这里的情况。看起来std::basic_fstream 使用了一些字符编码机制,并且由于为char 定义了一个但没有为unsigned char 定义一个,因此在尝试使用“错误”字符数据类型时文件流会静默失败......就是这样至少我看到了。

但这也是我不明白的。不需要任何字符编码。我什至不以文本模式打开文件,我想处理二进制数据。这就是为什么我使用uint8_t 类型的数组,而不是char,使用这种数据类型而不是普通的旧char 感觉更自然。但在我决定放弃 uint8_t 数据类型并接受使用 char 缓冲区,或开始使用定义为 char 的自定义 byte 数据类型数组之前,我想问两个问题:

  1. 究竟是什么机制阻止我使用无符号字符数据类型?它真的与字符编码有关,还是有其他用途?为什么文件流适用于有符号字符数据类型,但不适用于无符号数据类型?
  2. 假设我仍然想使用std::basic_fstream&lt;uint8_t&gt;,不管它有多么(不)合理 - 有什么方法可以实现吗?

【问题讨论】:

  • 流的内部结构根本不支持unsigned char。让它正常使用char,您只需对write() 执行类型转换,例如:std::ofstream file("file.name", std::ios::binary); ... file.write(reinterpret_cast&lt;char*&gt;(buf), BUFSIZE);
  • 我知道,但是太丑了。
  • 别担心reinterpret_cast,妈妈还是爱你的。
  • @PookyFan 丑不丑,不过这是你必须要做的事情
  • 是的,要么使用 char 数组。但这整个问题都来自于寻找替代解决方案,因为这两种解决方案都没有真正吸引我。但如果我要从两者中进行选择,我想我宁愿使用兼容的数组,也不愿将指针仅用于写入数据,特别是如果代码中有更多的write() 调用。

标签: c++ std fstream codecvt char-traits


【解决方案1】:

std::basic_fstream&lt;unsigned char&gt; 不起作用,因为它使用了std::char_traits&lt;unsigned char&gt;,但标准库没有提供这样的专门化功能,请参阅std::char_traits 了解详细信息。

如果你想读/写二进制数据,你需要使用std::basic_fstream&lt;char&gt;,用std::ios_base::binary标志打开它,然后使用std::basic_ostream&lt;CharT,Traits&gt;::write函数写入二进制数据。

这是一个遗留问题,因为所有char 类型都可以用来表示二进制数据。标准库使用 char 可能是因为它是完成这项工作的最短的输入和阅读。


究竟是什么机制阻止我使用无符号字符数据类型?

没有std::char_traits&lt;unsigned char&gt; 专业化。

它真的与字符编码有关,还是有其他用途?

std::char_traits 在其接口中精确定义了一些用途,但不包括解码/编码。后者由codecvt 完成,参见那里的用法示例。

为什么文件流适用于有符号字符数据类型,但不适用于无符号数据类型?

因为std::basic_ostream&lt;CharT,Traits&gt;::write 接受CharT,这是您为流指定的第一个模板参数。它写入与读取相同的字符类型,并使用 codecvt 将 CharT 转换为字节。

假设我仍然想使用std::basic_fstream&lt;uint8_t&gt;,不管它有多么(不)合理——有什么办法可以实现吗?

标准类和函数模板不能专门用于内置类型if I am not mistaken。您需要使用std::char_traits 接口创建另一个类,并将其指定为标准流的第二个模板参数。我想,你需要一个非常强大(哲学)的理由来卷起袖子去做。

如果您不这样做,您可以继续使用std::fstream&lt;char&gt; 并使用stream.write(reinterpret_cast&lt;char const*&gt;(buf), sizeof buf);。

【讨论】:

  • 好吧,但我仍然想知道第二个问题的答案,因为它让我很烦恼。即使没有定义std::char_traits&lt;unsigned char&gt;,我能否以某种方式自己定义它并用它“提供”文件流对象(即不会使我的代码过于复杂)。如果答案是“否”(因为 std 类设计不允许这样做,或者会要求我为此目的创建子类),那么就这样吧 - 我可以使用 char 形式的二进制数据s。但出于好奇,我想知道如何让我的原始解决方案发挥作用,如果有的话。
  • @PookyFan 我认为您更新了问题,让我重新阅读并更新我的答案。
  • 嗯,这是一个非常详尽的编辑,内容丰富。谢谢你,我会投票并接受你的回答。
  • @PookyFan 谢谢你的好话,这是我不能不奖励的东西。
  • @PookyFan 要更加迂腐,你必须至少创建另一个std::char_traits 和另一个std::codecvt unsigned char .
【解决方案2】:

其实char和uint8_t可以是不同的类型。这也意味着他们可以有不同的std::char_traits。字符特征类型是std::basic_fstream的第二个模板参数,默认为std::char_traits用字符类型实例化。 std::basic_fstream 默认通过字符特征模板参数进行格式化 I/O。它不会简单地重定向原始字节不变。这可能就是您得到不同结果的原因。

【讨论】:

  • (u)int8_t 作为一个独特的类型是可选的,它可能只是(unsigned) char 的别名。这是由编译器供应商决定的。
  • @RemyLebeau 是的,我的意思是它们不一定相同。
  • 但是你说的很对,signed char、unsigned char 和 char 是不同的类型。 uint8_t 必须是无符号的,但是,char 的签名取决于目标架构,gcc 有一个char 签名的命令行选项,因此uint8_t 应该明确定义为@987654337 @ 是普遍健壮的,没有令人讨厌的意外(不要破坏最小意外的基本工程原则)。在不同的体系结构上将uint8_t 定义为char 或unsigned char 将导致出现困难的错误。
猜你喜欢
  • 2021-09-08
  • 2023-03-18
  • 2011-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-07
  • 1970-01-01
  • 2013-01-19
相关资源
最近更新 更多