【问题标题】:Risking the exception anti-pattern.. with some modifications冒着异常反模式的风险..进行一些修改
【发布时间】:2010-05-20 02:42:30
【问题描述】:

假设我有一个在某些机器上运行 24x7 的库。即使代码坚如磐石,硬件故障也迟早会触发异常。我想为这样的事件设置某种故障保护装置。一种方法是编写封装每个 api a 的包装函数:

returnCode=DEFAULT;
try
{
  returnCode=libraryAPI1();
 }
catch(...)
{
 returnCode=BAD;
}
return returnCode;

库的调用者然后重新启动整个线程,如果 returnCode 错误则重新初始化模块。

事情可能会大错特错。例如

如果 try 块(或 libraryAPI1())有:

 func1();
 char *x=malloc(1000);
 func2();

如果 func2() 抛出异常,x 将永远不会被释放。同样,文件损坏也是一种可能的结果。

您能告诉我在这种情况下还有哪些可能出错的地方吗?

【问题讨论】:

  • 关于示例,这正是RAII 解决的问题。
  • 我对类似情况的“解决方案”(一个 24/7 运行的守护程序)只是让一个 cron 作业每 2 分钟检查一次以确保守护程序仍在运行,并在必要时重新启动它.当抛出一个罕见的异常时,让守护进程死掉并在几分钟后重新启动通常没什么大不了的。
  • 如果您认为“硬件故障可能导致此代码中断”,那么您认为可以编写代码来修复它的原因是什么?您的恢复代码也会受到影响。
  • 阅读这篇文章:stackoverflow.com/questions/161177/… 以了解为什么 finally 是由 RAII 解决的糟糕的语言设计

标签: c++ anti-patterns


【解决方案1】:

这段代码:

func1();
char *x=malloc(1000);
func2();

不是 C++ 代码。这就是人们所说的带有类的 C。它是一种看起来像 C++ 但与 C++ 在现实生活中的使用方式不匹配的程序风格。原因是;良好的异常安全 C++ 代码实际上从不需要(直接)在代码中使用指针,因为指针始终包含在一个专门设计用于在异常安全庄园(通常是智能指针或容器)中管理其生命周期的类中。

该代码的 C++ 等效项是:

func1();
std::vector<char> x(1000);
func2();

【讨论】:

    【解决方案2】:

    硬件故障可能不会导致 C++ 异常。在某些系统上,硬件异常是与 C++ 异常完全不同的机制。在其他方面,C++ 异常建立在硬件异常机制之上。所以这不是一个真正的一般设计问题。

    如果您希望能够恢复,则需要进行事务处理——每个状态更改都需要运行到完成或完全退出。 RAII 就是其中的一部分。正如 Chris Becke 在另一个答案中指出的那样,除了资源获取之外,还有更多需要说明的内容。

    有一个在交易中经常使用的复制修改交换习惯用法,但如果您尝试调整工作代码来处理这种百万分之一的情况,这可能太繁重了。

    如果您确实需要健壮性,则将代码隔离到一个进程中。如果硬件故障导致进程终止,您可以让看门狗重新启动它。操作系统将回收丢失的资源。您的代码只需要担心具有持久状态的事务性,例如保存到文件中的内容。

    【讨论】:

      【解决方案3】:

      您可以控制 libraryAPI 的实现吗?

      如果它适合OO模型,你需要使用RAII模式来设计它,这保证了析构函数(谁将释放获取的资源)在异常时被调用。

      使用智能指针等资源管理助手也有帮助

      try
      {
          someNormalFunction();
          cSmartPtr<BYTE> pBuf = malloc(1000);
          someExceptionThrowingFunction();    
      }
      catch(...)
      {
          // Do logging and other necessary actions
          // but no cleaning required for <pBuf>
      }
      

      【讨论】:

      • 是的,我有源代码。我的问题不是如何修复示例,而是我可能遇到的其他问题。如果我选择使用此包装器并进行重构,您的回答会很有帮助。
      • 据我了解,您正在系统中实施软件“看门狗”。虽然您可以从大多数异常中恢复,但在某些情况下您不能假装事情从未发生并继续运行,即堆栈损坏:)
      • 您应该始终在 C++ 中使用 RAII 进行资源管理。保护堆栈分配对象中的资源,该对象的析构函数执行清理。然后你几乎可以删除所有的 try/catch 子句。
      • 在 RAII 实施后,您将需要一定的故障控制(try/catch)。
      【解决方案4】:

      exeptions 的问题是——即使你用 RAiI 重新设计——它仍然很容易使代码变得不同步:

      void SomeClass::SomeMethod()
      {
        this->stateA++;
        SomeOtherMethod();
        this->stateB++;
      }
      

      现在,该示例可能看起来是人为的,但如果您将 stateA++ 和 stateB++ 替换为以某种方式更改类状态的操作,则此类的预期结果是状态保持同步。 RAII 可能会在使用异常时解决与状态相关的一些问题,但它所做的只是提供一种错误的安全感 - 如果 SomeOtherMethod() 抛出异常,则需要分析所有周围的代码以确保后置条件(stateA. delta == stateB.delta) 得到满足。

      【讨论】:

      • RAII 只为资源提供故障安全处理。它不能保护操作/过程不崩溃。
      • RAII 技术可以用来轻松解决这个问题,如果你使用 resource 的慷慨定义。
      猜你喜欢
      • 2011-01-07
      • 1970-01-01
      • 2011-08-17
      • 1970-01-01
      • 2015-09-23
      • 1970-01-01
      • 2020-01-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多