具有良好设计的异常机制的C++项目

7
有人知道一个开源的C++应用程序,带有良好设计/稳健的异常机制,这样我就可以得到一些灵感吗? 我看到的大多数代码/示例都会做出可疑的事情,例如:
  1. 抛出带有消息字符串作为参数的对象。 这似乎是错误的,因为它将异常标记为致命的,一个错误消息可以在更高层次上显示给用户,留下很少的空间供客户端代码尝试处理异常。 即使异常是致命的,像不同的语言环境一样,似乎在抛出点格式化消息是一个坏主意。
  2. 使用大量从基本异常类派生的不同异常类。 为每个可能出错的事情(打开文件、读取文件、写入文件、创建线程等)引入一个新的类/类型仅仅是感觉上不对。 在最高级别捕获所有未处理的异常时,使用基本类型会丢失显示有意义的错误消息所需的类型信息。
  3. 为每个组件/库使用从基本异常类派生的一个异常类,并将其作为参数赋予一个错误代码以指示确切的错误。 使用基本类型捕获会导致模棱两可。(我们捕获了谁的错误代码“3”?)...
欢迎给出一些正确的方向指引。

我见过的最简单的C++异常机制是Symbian的实现。一切都是int,没有异常类或消息。'Leave'与异常同义:http://www.developer.nokia.com/Community/Wiki/Using_TRAP - James
1
(1) 异常字符串是为程序员而非用户设计的,请勿在此处使用本地化。 (2) 否则编译器如何确定正确的展开位置呢? (3) 显然,这就是错误代码的问题所在。请不要这样做。 - Mooing Duck
@James:简单并不总是好的。在那种情况下,我会称其为糟糕的。 - Mooing Duck
如果你正在使用基本类型来处理异常,那么你可能做错了。 - Mooing Duck
@Mooing Duck:为什么这样说?它运行得非常好。我认为为异常考虑一个类层次结构完全是过度设计。 - James
1个回答

6
为了解决这三个问题,我发现对我来说最好的方法是抛出自己的定制异常,该异常源自于std::runtime_error。就像这样:
#include <exception>

class ChrisAException : public std::runtime_error
{
      /// constructor only which passes message to base class
      ChrisAException(std::string msg)
      : std::runtime_error(msg)
      {

      }
}

它允许接受一个字符串,我通常会按照以下格式输入(假设x为负数是无效的输入,表示调用它的内容有误):

#include "ChrisAException.h"

void myfunction(int x)
{

    if(x < 0) 
    {
        throw ChrisAException("myfunction(): input was negative!");
    }

    // rest of your function
} 

请记住,这些异常中的字符串更多地是为程序员而不是最终用户设计的。接口的程序员应该在本地化失败时显示有意义的内容。异常中的字符串可以被记录或在调试时查看(最好!)

这样,最终您可以使用以下方式捕获它:

try
{
      // high level code ultimately calling myfunction

}
catch(ChrisAException &cae)
{
       // then log cae.what()
}
catch(std::runtime_error &e)
{
       // this will also catch ChrisAException's if the above block wasn't there
}
catch(...)
{
      // caught something unknown
}

我个人不喜欢定义过多的异常类型或给出错误代码,我让字符串消息来报告问题。
通常情况下,我使用C++异常表示"程序出现了问题",并且不用于处理正常情况。因此,在算法执行期间抛出的异常要么表示"向用户标记出现了问题",要么表示"不告诉用户"(取决于它对他们正在进行的操作有多重要),但肯定会记录日志,并以某种方式通知程序员。
我不使用C++异常来处理非本质上是编程错误的情况,例如,某些不正确的逻辑或被错误调用的情况。例如,我不会使用C++异常来处理像DVD写入程序中没有空白DVD这样的正常程序情况。对于这种情况,我会有明确的返回代码,让用户知道是否存在空白DVD(可能会弹出一个对话框等)。
请记住,C++异常处理的一部分是展开堆栈到try-catch块。对我来说,这意味着中止程序中正在进行的操作并清理堆栈。对于像DVD示例这样的问题,您不应该希望展开大量的堆栈。它并不是灾难性的。你应该只是让用户知道然后再让他们重试。
但是,这是我使用C++异常的首选方式,基于我的经验和阅读。我也可以接受其他意见。
编辑:根据评论者的建议,将std::exception更改为std::runtime_error

最高级别的捕获代码如何确定异常的类型,以便尝试处理它或为用户打印错误消息?比较哪个字符串? - Unimportant
3
在 MSVC(不符合规范的)实现中,std::exception没有接受字符串参数的构造函数。虽然不是特别重要,但仍需注意。对于遵循规范的编译器,您可以考虑使用std::runtime_error代替。 - Tamás Szelei
你无法处理 catch(...),因此该块应该 始终throw; 结束以继续退回。 - Mooing Duck
1
如果您无法“修复”导致错误的情况(因为您甚至不知道错误的类型),那么程序将处于未知状态。在这种情况下,唯一“正确”的做法是崩溃/重启。互联网上有很多关于此的文章。 - Mooing Duck
2
@MooingDuck:如果进入catch(...)块,程序如何保持在未知状态?已经进行了解旋,析构函数运行,事务被回滚,内存被释放。出现了问题,但您的不变量受到了保护。 - Ben Voigt
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接