【问题标题】:String conversion errors: exceptions or error codes?字符串转换错误:异常还是错误代码?
【发布时间】:2012-09-15 15:28:58
【问题描述】:

在编写一个函数以在不同编码的字符串之间进行转换(例如从 UTF-8 到 UTF-16),处理错误的最佳方法是什么(例如无效的输入 UTF-8 字节序列)?抛出异常或返回错误代码(甚至是bool)?

// Throws a C++ exception on error. 
std::wstring ConvertFromUtf8ToUtf16(const std::string& utf8);

// Returns true on success, false on error.
bool ConvertFromUtf8ToUtf16(std::wstring& utf16, const std::string& utf8);

使用异常,可以进行链式函数调用(当函数返回值用作其他函数/方法的输入时)。

但我不确定在这种情况下使用异常是否好;我在想 Eric Lippert in his quality blog post 所说的令人烦恼的异常(以及相关的 Int32.Parse()/TryParse() 示例)。

例如,如果使用异常,则应强制调用者将函数调用包装在 try/catch 块中,以检查无效 UTF-8 输入的大小写:

try
{
   wstring utf16 = ConvertFromUtf8ToUtf16(utf8);
}
catch(const Utf8ConversionException& e)
{
   // Bad UTF-8 byte sequence
   ...
}

这对我来说似乎并不理想。

也许最好的办法是只提供 both 重载(在非抛出重载中实现转换代码,而在抛出重载中只需调用非抛出版本,以防万一的错误返回码抛出异常)?

【问题讨论】:

  • 从 utf-8 到 utf-16 的转换会出现哪些错误? (提示:验证输入应该与转换完全分开)
  • 如果转换为 UTF-16,结果应该是 std::u16string,而不是 std::wstring。后者有一个very specific purpose
  • 假设从 UTF-8 转换在实践中几乎总能成功,使用异常处理错误并非不合理。

标签: c++ exception exception-handling


【解决方案1】:

一个准则是考虑如果用户忽略不知道他们应该检查您返回的错误代码会发生什么。

  • 如果代码理论上可以在遇到错误时继续运行,则返回错误可能被认为是合理的。正如您所提到的,代码看起来更干净。
  • 如果稍后忽略错误可能会导致非常糟糕的行为,那么抛出异常可能是一个更好的主意。
  • 第三种可能的选择在某种程度上平衡了错误代码的简洁性并迫使程序员意识到潜在的错误是使函数需要对错误代码的引用。这也适用于导出的库和不能有效处理异常的(大多数较旧的)编译器。

    StringConversionResult result; // Could be a "success" bool

    wstring utf16 = ConvertFromUtf8ToUtf16(utf8, result);

【讨论】:

    【解决方案2】:

    如果此函数是从库中导出的,请使用返回码。当库和客户端使用不同的 C/C++ 运行时库构建时,从导出的函数中抛出异常可能会使程序崩溃。通常,这是未定义的行为。

    对于内部使用,我相信异常是更好的选择。您正在谈论的情况,当调用者不使用 catch 块时,程序会立即崩溃(未处理的异常)。这样会更好,然后在未来某个时间点继续执行程序,但结果未定义。

    【讨论】:

    • 是的,我知道 C++ 异常不能安全地跨越模块边界(除非使用相同的 C++ 编译器、相同的 CRT 和相同的编译器设置)。但这不是问题的重点:事实上,按照这种思路,在模块边界也不支持在接口处使用 STL 类(如 STL 字符串)(同样,除非使用相同的 C++ 编译器/CRT/设置)。
    【解决方案3】:

    只有三个选择。第一个是“用错误代码点替换所有失败”——Unicode 标准提供了几个替换代码点。这在某些情况下很好。二是抛出异常。三是提供错误函数对象,失败时调用。例如,

    bool fail = false;
    std::u16string str = ConvertFromUTF8ToUTF16(utf8, [&] {
        return u16"default";
        // or
        throw std::runtime_error("fail");
        // or
        fail = true;
    });
    

    关键是,在任何情况下,您都不会依赖用户来检查失败 - 如果他什么都不做,那么他的函数不会继续,编译器会哭,或者函数可以继续。

    返回错误代码不是一种选择——这很容易出错。

    【讨论】:

      猜你喜欢
      • 2016-10-07
      • 2014-01-21
      • 1970-01-01
      • 2013-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-28
      • 1970-01-01
      相关资源
      最近更新 更多