gpt4 book ai didi

c++ - 使用 STL 的迭代器上的 UTF-8 到 UTF-32

转载 作者:太空狗 更新时间:2023-10-29 21:17:56 24 4
gpt4 key购买 nike

我有一个字符迭代器 - 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 有一个用于此的迭代器,但我想避免不是仅包含 header 的提升库,这感觉像是 STL 应该能够做到的。

最佳答案

我认为您不能直接使用 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无论如何,您可以从输入迭代器序列中复制更大的 block 并转换整个 block 。这将减少看到不完整的多字节序列的可能性,因为如果 block 以不完整的序列结尾,则只有 block 末尾的最后 1-3 个字节需要重新处理, block 中较早的所有内容都将被转换.

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

关于c++ - 使用 STL 的迭代器上的 UTF-8 到 UTF-32,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31270482/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com