【问题标题】:Exceptions with Unicode what()Unicode 异常 what()
【发布时间】:2011-04-15 05:08:56
【问题描述】:

或者,“俄罗斯人如何抛出异常?”

std::exception的定义是:

namespace std {
  class exception {
  public:
    exception() throw();
    exception(const exception&) throw();
    exception& operator=(const exception&) throw();
    virtual ~exception() throw();
    virtual const char* what() const throw();
  };
}

用于设计异常层次结构的popular school of thought 是从 std::exception 派生的:

一般来说,最好扔东西, 不是内置插件。如果可能的话,你应该 抛出派生类的实例 (最终)来自 std::exception 班级。通过使您的异常类 (最终)从标准继承 异常基类,您正在制作 让您的用户生活更轻松(他们有 捕捉大多数东西的选项 std::exception),另外你可能是 为他们提供更多信息 (例如,您的特定 例外可能是对 std::runtime_error 或其他)。

但是面对Unicode,设计一个同时实现以下两个的异常层次结构似乎是不可能的:

  • 最终从 std::exception 派生,以便在捕获站点使用
  • 提供 Unicode 兼容性,因此诊断不会被分割或乱码

想出一个可以用 Unicode 字符串构造的异常类很简单。但是标准规定 what() 必须返回一个 const char*,因此在某些时候必须将输入字符串转换为 ASCII。无论是在构建时完成还是在调用 what() 时完成(如果源字符串使用 7 位 ASCII 无法表示的字符),可能无法在不丢失保真度的情况下格式化消息。

您如何设计一个异常层次结构,将 std::exception 派生类的无缝集成与无损 Unicode 诊断相结合?

【问题讨论】:

  • 没什么大不了的,只是使用使用字节的编码。 IMO std:.exception 的更大问题是派生类非虚拟派生自它。因此,您无法从您自己的基类派生,派生自std::exception,例如std::out_of_range
  • @sbi: 是的,但我通过仅直接根据std::exception 定义我的层次结构来避开这个问题。我抛出我自己的std::exception 派生异常并将其他标准定义的异常留给标准库。可以肯定的是,这不是一个理想的解决方案,但对于我的使用来说,考虑到标准的当前状态,它是最好的解决方案。
  • 刚刚注意到:似乎与以下内容重复:stackoverflow.com/questions/618111/…
  • 在苏维埃俄罗斯,你会遇到异常。

标签: c++ c++11


【解决方案1】:

char* 不代表 ASCII。您可以使用 8 位 Unicode 编码,如 UTF-8。 char 也可以是 16 位或更多,然后您可以使用 UTF-16。

【讨论】:

  • 采用 UTF-8 路径的额外好处是 STL 等异常文本字符串已经是有效的 UTF-8。问题是,一旦通过 7 位代码点,处理起来有点麻烦。那时,您将需要 UTF-8 的自定义输出例程或到 8 位或 16 位代码页的转换例程,所有这些都可能是您想要在异常处理程序中执行的操作,也可能不是。
  • @Andreas:将std::string 用于UTF-8 时有两个问题:一个是在UTF-8 中,字符串中的字符数和字节数之间存在差异。另一个是很容易混淆系统编码的字符串(每个应用程序都将继续需要)和 UTF-8 编码的字符串,从而导致向用户显示有趣的文本。我发现将std::basic_string<signed char> 用于UTF-8 编码的字符串会更好。这至少消除了第二个问题,因为当你混淆编码时,它会让编译器对你咆哮。
  • 使用 ASCII 子集之外的字符的系统编码字符串有多普遍?如果系统编码的字符串可以被限制为 ASCII 子集,那么 UTF-8 可以在没有有趣文本的情况下使用。至于字符串长度,我喜欢使用std::string,因为我可以从中获取字节数,并且可以计算 O(n) 中的字符数。基本上,如果您希望字符串以字符形式思考,您必须继承 std::basic_string<signed char>,更改其迭代器(并可能将其从随机访问迭代器降级),并添加字节计数方法。
  • @sbi:我想你误解了我的意思,我的意思是从 what() 返回的 stdlib 异常的文本字符串已经是有效的 UTF-8 字符串,因为它们是 ASCII 并且 ASCII 是一个子集UTF-8 的。此外,我从您的两个问题中创建了一个大的“麻烦问题”,因为 UTF-8 的所有问题都是在您移出 ASCII 子集时开始的。说到解决方案,我非常喜欢下面ybungalobill 发布的帖子中接受的答案。
【解决方案2】:

返回 UTF-8 是一个显而易见的选择。如果使用您的异常的应用程序使用不同的多字节编码,那么它可能很难显示字符串。 (它不能知道它是 UTF-8,可以吗?) 另一方面,对于 ISO-8859-* 8 位编码(西欧、西里尔文等),显示 UTF-8 字符串将“只是”显示一些乱码,如果您不能这样做,您(或您的用户)可能会很好顺便说一句,消除歧义。语言环境字符集和 UTF-8 中的 char*。

我个人认为只有低级错误消息应该进入 what() 字符串,我个人认为这些应该是英文的。 (可能结合了一些错误号或诸如此类的东西。)

我看到的what() 最糟糕的问题是在what() 消息中包含一些上下文细节并不少见,例如文件名。文件名通常是 ASCII,因此您别无选择,只能使用 UTF-8 作为what() 编码。

另请注意,您的异常类(从 std::exception 派生)显然可以提供您喜欢的任何访问方法,因此添加显式 what_utf8()what_utf16()what_iso8859_5() 可能是有意义的。

编辑:关于 John 关于如何返回 UTF-8 的评论:

如果你有一个const char* what() 函数,这个函数本质上会返回一堆字节。在西欧 windows 平台上,这些字节通常会被编码为 Win1252,但在俄罗斯 windows 上,它可能会被编码为 Win1251

字节返回的含义取决于它们的编码,而它们的编码取决于它们“来自”的位置(以及解释它们的人)。字符串文字的编码是在编译时定义的,但在运行时仍然取决于应用程序如何解释这些。

因此,要让您的异常返回带有 what()(或 what_utf8())的 UTF-8 字符串,您必须确保:

  • 异常的输入消息具有明确定义的编码
  • 您为用于保存消息的字符串成员定义了明确的编码。
  • 你在调用what()时适当地转换编码

例子:

struct MyExc : virtual public std::exception {
  MyExc(const char* msg)
  : exception(msg)
  { }
  std::string what_utf8() {
    return convert_iso8859_1_to_utf8( what() );
  }
};

// In a ISO-8859-1 encoded source file
const char* my_err_msg = "ISO-8859-1 ... äöüß ...";
...
throw MyExc(my_err_msg);
...
catch(MyExc const& e) {
  std::string iso8859_1_msg = e.what();
  std::string utf_msg = e.what_utf8();
...

转换也可以放在 MyExc() 的(覆盖的)what() 成员函数中您可以定义异常以采用已经 UTF-8 编码的字符串 或 您可以在 ctor 中转换(从预期的输入编码,可能是 wchar_t/UTF-16)。

【讨论】:

  • "返回 UTF-8 是一个显而易见的选择。"这似乎遵循当前思想的弧线。现在唯一的问题是,我如何返回 UTF-8? :)
  • @John Dibling:如果您的消息文本都是英文并且可以用标准ASCII 表示,那么您已经做得足够了,因为ASCII 和UTF-8 的前128 个字符是相同的。如果您使用字符和高于 127 的编码,则需要将编码转换为 UTF-8。现在必须有一个标准的 C++ 库函数来做到这一点。如果没有,libiconv 可以解决问题。
  • @JeremyP:我们在我工作的地方使用 ICU 来处理 Unicode,当然不是完美的(C 接口...),但它可以完成工作并处理 Unicode/国际化/本地化的怪癖。跨度>
  • @Matthieu M:谢谢。我正在寻找一个与 C 兼容的 unicode 库。我本可以使用 libiconv,但它的许可证更严格。
  • @JeremyP:很高兴能帮上忙 :)
【解决方案3】:

第一个问题是你打算用 what() 字符串做什么?

您打算在某处记录信息吗?

如果是这样,您不应该使用 what() 字符串的内容,您应该使用该字符串作为参考来查找正确的 local 特定日志消息。所以对我来说,what() 的内容不是用于记录目的(或任何形式的显示),它是一种查找实际记录字符串(可以是任何 Unicode 字符串)的方法。

现在; what() 字符串可以是 us-full 以包含人类可读的消息,以帮助开发人员快速调试(但对于这种高度可读的修饰文本不是必需的)。因此,没有理由支持 ASCII 以外的任何东西。遵守 KISS 原则。

【讨论】:

  • 回答您的问题。我想使用what() 字符串来生成两个级别的诊断。较低级别是以开发人员或技术人员为中心的诊断,将显示在日志文件中。但在更高的层次上,我希望这些字符串用于构建可由正常人操作的诊断。正如您似乎暗示的那样,what() return 可能只是一个查找值,用于更人性化的消息表,但字符串的某些组件(或至少是例外)需要是人类可读的,例如“找不到文件 blah.txt。”
  • 我的另一个目标是将catch 块保持在最低限度。 Utopia 将拥有一个捕获所有内容的 catch( const std::exception& ex ) 块,该块将使用 what() 字符串来生成技术人员和人类级别的诊断。按照这种模式,构建这两条消息的所有数据都必须可以从 what() 字符串中检索。
  • 大多数本地转换语言采用输入字符串并通过资源将其转换为本地字符串。因此,如果您说字符串的第一部分直到冒号用于查找本地字符串,您可以这样做:File could not be found: blah.txt。然后可以使用File could not be found: 部分查找本地特定翻译。
【解决方案4】:

const char* 不必指向 ASCII 字符串;它可以是多字节编码,例如 UTF-8。一种选择是使用wcstombs() 和朋友将wstrings 转换为字符串,但您可能必须在打印之前将what() 的结果转换回wstring。它还涉及到更多的复制和内存分配,这超出了您在异常处理程序中的承受能力。

我通常只定义自己的异常基类,它在构造函数中使用 wstring 而不是 string,并从 what() 返回一个 const wstring&。这没什么大不了的。缺乏标准是一个很大的疏忽。

另一个有效的观点是异常字符串永远不应该呈现给用户,因此没有必要对它们进行本地化,因此您不必担心上述任何问题。

【讨论】:

  • +1 恕我直言,创建自己的异常类是最合理的做法。如果你捕获了一个 std::exception ,如果编码未知(是 CP1252 还是 UTF-8 ?)与它没有太大关系。如果你有自己的异常类,问题就解决了。
【解决方案5】:

标准没有指定 what() 返回的字符串是什么编码,也没有任何事实上的标准。在我的项目中,我只是将其编码为 UTF-8 并从 what() 返回。当然可能与其他库不兼容。

另见:https://stackoverflow.com/questions/1049947/should-utf-16-be-considered-harmful 为什么 UTF-8 是不错的选择。

【讨论】:

    【解决方案6】:

    在错误处理中添加unicode比较好:

    try
    {
       // some code
    }
    catch (std::exception & ex)
    {
        report_problem(ex.what())
    }
    

    还有:

    void report_problem(char const * const)
    {
       // here we can convert char to wchar_t or do some more else
       // log it, save to file or message to user
    }
    

    【讨论】:

      【解决方案7】:

      what() 通常并不意味着向用户显示消息。除此之外,它返回的文本是不可本地化的(即使它是 Unicode)。我只是使用 what() 来向作为开发人员的您显示一些有价值的东西(例如引发异常的地方的源文件和行号),对于那种文本,ASCII 通常绰绰有余。

      【讨论】:

      • 这是你的意见,虽然我尊重你的意见,但我不同意。即使what() 输出仅存储到日志文件中,它也处于“呈现给用户”的某种级别,并且不需要乱码。
      • 我并不是说它应该是胡言乱语。我是说 what() 不适合保存“国际”文本,不是因为它不能保存 Unicode(它可以),而是因为它不可本地化。
      • 当然,异常文本可能不需要像用户通常看到的文本那样“国际化”。但我可以想象一段 Unicode 文本仍然非常相关并且希望将其包含在例外中的时候。例如,文件名或路径可能包含 Unicode 字符。忽略它会降低异常处理或日志记录的用处。
      • 为什么不能国际化呢? what内不能访问本地吗?
      【解决方案8】:

      The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) 乔尔·斯波尔斯基(Joel Spolsky)

      编辑:制作 CW,评论者可以根据需要编辑此链接相关的原因

      【讨论】:

      • -1 :我认为添加一个链接(一个很好的链接,顺便说一句。)没有任何解释这与 C++ 异常有何关系nothing 可以帮助回答 问题。 (它可能有助于将一些编码问题与上下文联系起来,但是 cmets 的用途是什么,不是吗?)如果 OP 确实需要读取链接,则尤其如此。
      • 此外,我已经阅读了该链接,但它并没有解决我的问题。
      • 相反,我认为这个链接很好地解释了为什么使用char const* 与字符编码无关。
      • @Alexandre:但是对于 SO 上的读者来说,没有迹象表明 为什么我应该在外部网站上阅读这篇长篇文章。正如@Martin 所说,不要只发布链接,而是发布简短摘要和/或说明为什么链接是相关的。
      猜你喜欢
      • 2018-06-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-28
      • 1970-01-01
      相关资源
      最近更新 更多