【问题标题】:UTF-8 to UTF-32 on iterators using the STL使用 STL 的迭代器上的 UTF-8 到 UTF-32
【发布时间】:2015-09-25 01:17:46
【问题描述】:

我有一个 char 迭代器 - 一个 std::istreambuf_iterator<char> 包装在几个适配器中 - 产生 UTF-8 字节。我想从中读取一个 UTF-32 字符(char32_t)。我可以使用 STL 这样做吗?怎么样?

std::codecvt_utf8<char32_t>,但显然只适用于char*,而不是任意迭代器。

这是我的代码的简化版本:

#include <iostream>
#include <sstream>
#include <iterator>

// in the real code some boost adaptors etc. are involved
// but the important point is: we're dealing with a char iterator.
typedef std::istreambuf_iterator< char > iterator;

char32_t read_code_point( iterator& it, const iterator& end )
{
    // how do I do this conversion?
    // codecvt_utf8<char32_t>::in() only works on char*
    return U'\0';
}

int main()
{
    // actual code uses std::istream so it works on strings, files etc.
    // but that's irrelevant for the question
    std::stringstream stream( u8"\u00FF" );
    iterator it( stream );
    iterator end;
    char32_t c = read_code_point( it, end );
    std::cout << std::boolalpha << ( c == U'\u00FF' ) << std::endl;
    return 0;
}

我知道 Boost.Regex 对此有一个迭代器,但我想避免使用非标头的 boost 库,这感觉就像 STL 应该具备的能力。

【问题讨论】:

  • 不要使用stl 标记,除非您真的指的是 STL,即 1990 年代的库,大部分 C++ 标准库都源自该库
  • 哦,我明白了。感谢您的提示。
  • @JonathanWakely 常见用途、标签 wiki、标签缩写形式以及该术语的实用性(谁,真的,不再谈论原始 STL 了?)有点不同意你的看法。我的意思是,迂腐很有趣,但 SO 不仅仅是有趣。
  • @Yakk,不,标签 wiki 说将整个 C++ 标准库称为 STL 是不正确的。无论如何,std::codecvt_utf8 是 Locales 子句的一部分,这很好地证明了我的观点,因为std::locale 不是模板,也不是“STL”的一部分。没有人再谈论 STL 的事实是避免使用标签的好理由,not 用它来表示不同的意思。标准库的重要部分是不是模板(std::thread,有人吗?)所以这是错误的。 c++ 标记完全可以解决有关 C++ 标准库的问题!
  • @Yakk,标签缩写形式与您矛盾:“STL 的大部分被采用到标准库中,标准库中的 这些部分 有时也被引用集体作为...“(强调我的)语言环境从来都不是这些部分之一,codecvt_utf8 是 C++11 中的新内容,绝对不是这些部分之一!所以在所有方面都错了;-)

标签: c++ utf-8 iterator c++14


【解决方案1】:

我认为您不能直接使用codecvt_utf8 或任何其他标准库组件来执行此操作。要使用codecvt_utf8,您需要将迭代器流中的字节复制到缓冲区并转换缓冲区。

这样的事情应该可以工作:

char32_t read_code_point( iterator& it, const iterator& end )
{
  char32_t result;
  char32_t* resend = &result + 1;
  char32_t* resnext = &result;
  char buf[7];  // room for 3-byte UTF-8 BOM and a 4-byte UTF-8 character
  char* bufpos = buf;
  const char* const bufend = std::end(buf);
  std::codecvt_utf8<char32_t> cvt;
  while (bufpos != bufend && it != end)
  {
    *bufpos++ = *it++;
    std::mbstate_t st{};
    const char* be = bufpos;
    const char* bn = buf;
    auto conv = cvt.in(st, buf, be, bn, &result, resend, resnext);
    if (conv == std::codecvt_base::error)
      throw std::runtime_error("Invalid UTF-8 sequence");
    if (conv == std::codecvt_base::ok && bn == be)
      return result;
    // otherwise read another byte and try again
  }
  if (it == end)
    throw std::runtime_error("Incomplete UTF-8 sequence");
  throw std::runtime_error("No character read from first seven bytes");
}

这似乎做了比必要的工作更多的工作,在每次迭代时重新扫描 [buf, bufpos) 中的整个 UTF-8 序列(并对 codecvt_utf8::do_in 进行虚函数调用)。理论上codecvt_utf8::in 实现可以读取一个不完整的多字节序列并将状态信息存储在mbstate_t 参数中,以便下一次调用将从上一次中断的地方恢复,只消耗新字节,而不是重新处理不完整的已经见过的多字节序列。

但是,需要实现来使用mbstate_t 参数来存储调用之间的状态,实际上至少有一个codecvt_utf8::in 实现(我为GCC 编写的那个)不需要完全使用它。从我的实验看来,libc++ 实现似乎也没有使用它。这意味着它们在不完整的多字节序列之前停止转换,并让 from_next 指针(此处为 bn 参数)指向该不完整序列的开头,以便下一次调用应该从该位置开始并且(希望)提供足够的额外字节来完成序列并允许读取完整的 Unicode 字符并将其转换为char32_t。因为您只是尝试读取单个代码点,这意味着它根本不进行任何转换,因为在不完整的多字节序列之前停止意味着在第一个字节处停止。

有可能某些实现确实使用了mbstate_t 参数,因此您也可以修改上面的函数来处理这种情况,但是为了便于移植,它仍然需要处理以下实现忽略mbstate_t。支持这两种类型的实现会使函数相当复杂,所以我保持简单并编写了一个适用于所有实现的表单,即使它们确实使用mbstate_t。因为您一次最多只能读取 7 个字节(在最坏的情况下......平均情况可能只有一个或两个字节,具体取决于输入文本)重新扫描前几个字节的成本每次都不应该很大。

要从codecvt_utf8 获得更好的性能,您应该避免一次转换一个代码点,因为它是为转换字符数组而不是单个字符而设计的。由于您总是需要复制到char 缓冲区,因此您可以从输入迭代器序列中复制更大的块并转换整个块。这将减少看到不完整多字节序列的可能性,因为如果块以不完整的序列结束,则只需要重新处理块末尾的最后 1-3 个字节,块中较早的所有内容都将被转换.

为了获得更好的读取单个代码点的性能,您可能应该完全避免使用codecvt_utf8,并且要么自己动手(如果您只需要 UTF-8 到 UTF-32BE,这并不难),或者使用第三方库,例如 ICU .

【讨论】:

  • 是的,应该可以……我不太喜欢它,但它给了我一个想法;我会尝试一下。
  • 有点恶心——UTF-32 字符包含的字节数为 O(n^2),并且调用了 cvt 代码。鉴于上述内容的复杂性,自己动手几乎是有意义的。 (ik)
  • 不,没关系。我希望我可以一次调用codecvt_utf8&lt;char32_t&gt;::in() 一个字符,将mbstate_t 保持在中间,但这似乎不起作用。
  • 是的,我同意 Yakk 的观点,这就是为什么我说“我不太喜欢它”。 (旁注:我实际上已经推出了自己的产品,并且正要开始测试它,但我突然想到 C++ 标准库中可能存在一个现有的、正确的解决方案。)
  • @Mr.Wonko,允许实现使用mbstate_t 参数来保留调用之间的状态,但不是必须的。实际上很难这样做,因为mbstate_t 是由大多数 C++ 库供应商无法控制的底层 C 库定义的。我在 GCC 中实现 codecvt_utf8 所做的是忽略状态参数,只转换整个代码点,在不完整的多字节序列之前停止。根据我的实验,我认为 libc++ 实现也是如此。
猜你喜欢
  • 1970-01-01
  • 2012-08-19
  • 1970-01-01
  • 1970-01-01
  • 2017-06-20
  • 2020-01-28
  • 2011-02-10
  • 2014-08-21
  • 2013-06-12
相关资源
最近更新 更多