【问题标题】:Does exception handling require object-oriented programming?异常处理是否需要面向对象的编程?
【发布时间】:2012-01-24 16:12:51
【问题描述】:

在我的编程经验的这一点上,我意识到我是多么被宠坏了,在当今使用的大多数语言(C++、.Net、Java 等)中都可以使用异常处理,至少与 C 相比是这样。我准备好了参加高级 C 课程,并与我目前的范式相比,让我真正思考这些术语。

在 C 中,首先由程序员来防止错误发生,这对于习惯于异常处理的任何人来说都是相当令人生畏的。我突然想到,我遇到的任何具有异常处理的语言都恰好是面向对象的。至少据我所知,第一个具有异常处理功能的面向对象语言是 C++,它是 C 的一种演变。(如果我错了,请纠正我)

话虽如此,语言的面向对象特性是否允许异常处理,或者当面向对象语言真正开始变得司空见惯时,异常处理是作为一种特性添加的吗?什么是 C 缺少可以说的,C++,在机器代码中使 excpetion 起作用?

我发现这个post 是关于异常处理如何在后台工作的,但不确定该信息如何适用于我的问题(即,C 是否缺少通知、延续等?)。提前致谢。

【问题讨论】:

  • 有关异常处理的维基百科条目 (en.wikipedia.org/wiki/Exception_handling) 是了解此主题的良好起点。
  • 这些都不存在,只有 C 存在。异常处理只是一种抽象。 PS:你知道setjmp/longjmp吗?
  • 我认为你的前提有点缺陷。标准 ML(根据大多数定义)具有异常处理,但没有(根据大多数定义)OOP;同样是 Ada 95 之前的 Ada。你用过多少种非面向对象的语言?
  • @MooingDuck -- OO 语言不会“允许”堆栈展开,就像土豆允许烹饪一样。大多数 OO 语言出于某种原因需要堆栈展开,这有助于实现 EH。但是堆栈展开功能(即,检查调用堆栈和识别单个过程边界的能力)是语言支持环境的一个功能,可能由于其他原因(例如调试、支持过程范围等)而存在。
  • @HotLicks:我喜欢土豆的比喻。更一般地说,OO 语言不需要堆栈展开来处理异常。 任何 语言具有任何类型的控制流都需要continuation——“我接下来要做什么”数据结构——能够进行检查和更改。在许多 OO 语言中,数据结构恰好是一个堆栈,因此异常处理需要检查堆栈。但是 OO 语言并没有要求使用堆栈来具体化延续! OO 语言可以是无堆栈的。

标签: c++ c exception-handling


【解决方案1】:

C 在机器代码中没有任何内容,异常处理在 C 中过去和现在都很常见,setjmplongjmp

纯过程语言完全缺乏语言级功能的原因是,当不需要调用析构函数时,异常处理与setjmp 相同。异常处理以前在异国语言中出现过,但从未流行起来,因为它纯粹是语法糖。然而,一旦析构函数进入场景并且堆栈展开变得必要,语言级别的支持变得必要并且异常处理作为语言的一部分被广泛实施。

【讨论】:

  • 我很好奇:C 风格的异常如何处理清理中间分配?即使没有成熟的析构函数,也有人需要free 那段记忆,对吧?是否只是调用函数的责任来确保分配不被longjmp'd 左右?
  • @Nicol:您可以将异常视为 setjmp/longjmmp 与所谓“异常帧”的组合的“纯语法糖”。在 C 中,您可以创建每个线程的结构链表。每当你持有一些需要释放的资源,并且你想调用可以“抛出”的东西时,你将一个结构添加到列表中,调用这个东西,然后取消链接该结构。当有东西“抛出”时,它longjmps 到列表末尾的任何东西,那个东西可以释放该级别的资源,然后longjmp 到列表中的下一个东西......
  • ... 要“捕获”,您放置一个异常帧,当 longjmped 到该异常帧时,它不会继续列表,而是在此级别恢复执行。这可能是在 C 中为自己实现异常处理的最简单方法,如果不是最有效的话。
  • 在大多数 C++ 编译器中处理异常的方式是在抛出端,无法使用 setjmp 和 longjmp 来模仿。特别是,即使没有抛出异常,调用 setjmp 也会对每个 try 块强制执行成本。特别是 GCC 以昂贵的 throw 为代价具有基本上零成本的 try。我不相信您可以在 C 语言级别上复制它。
  • @BlueRaja-DannyPflughoeft:在这一点上,我们非常接近“结构化编程是/不是 goto 的语法糖”;-)
【解决方案2】:

异常处理是否需要面向对象编程?

没有。两者是完全分开的。一种可以使用没有异常处理作为控制流原语的 OO 语言,也可以使用非 OO 语言进行异常处理。

正如维基百科所指出的,面向对象编程是一种强调抽象封装消息传递价值的编程风格、模块化多态性继承,以实现对大型团队实施的复杂软件项目的低成本代码重用和有效管理.

您在该列表中看不到“循环”或“if 语句”或“goto”或“try-catch-finally-throw”,因为 控制流原语 与此无关抽象、封装、消息传递、模块化、多态性或继承被用于实现低成本代码重用或大型团队对复杂软件项目的有效管理。

在使异常起作用的机器代码中,C 缺少什么可以说,C++?

现代硬件在设计时肯定会考虑将异常处理作为控制流原语。 C 语言早在现代硬件出现之前就已经设计好了,这使得在 C 语言中实现异常处理变得更加困难,这种异常处理可以在 C 语言运行的所有硬件上高效运行。

但话虽如此,没有什么可以阻止您或其他任何人设计新版本的 C 语言,该版本将异常处理作为控制流原语,但没有 C++ 的所有其他特性。

如果您对如何将异常处理添加到支持延续的非 OO 语言这一主题感兴趣,请参阅我关于该主题的文章,其中概述了这个想法:

http://blogs.msdn.com/b/ericlippert/archive/2010/10/22/continuation-passing-style-revisited-part-two-handwaving-about-control-flow.aspx

【讨论】:

    【解决方案3】:

    话虽如此,语言的面向对象特性是否允许异常处理,或者当面向对象语言真正开始变得司空见惯时,异常处理作为一项功能添加了吗? p>

    我第一次知道例外是在 90 年代初我必须学习 Ada(学习 CS)时。 IIRC,Ada 有一个特殊的类型Exception。在那个时候,它还不是一种面向对象的语言。 (Ada95 添加了一些 OO 概念。)但是,我同意堆栈展开(即完全自动清理分配的资源)是异常处理成功的一个重要特征。将析构函数与异常处理相结合是 C++ 中异常成功的重要一点。

    我似乎还记得 Stroustrup 提到 Ada 对 C++ 中的异常处理产生了重大影响。

    【讨论】:

    • “堆栈展开是异常处理成功的重要特征”。在 C++ 中,就是这样。在 Java 中,您不需要堆栈展开,因为您有 GC。
    • +1 对——堆栈展开可能是关键部分。如果没有堆栈展开,您就只有 setjmp/longjmp,没有数据或控制范围,并且异常处理在很大程度上无法管理,除非作为全局“恐慌”机制。随着堆栈展开,EH 几乎“只是一个简单的编码问题”。堆栈展开是否可能取决于语言的支持环境,以及所涉及的调用/返回协议。
    • @Steve:GC 只处理单个资源。它不会关闭您的文件,也不会释放您的互斥锁。 AFAIK 您必须使用finally 明确执行此操作。我认为这是 C++ 的回归。
    • @SteveJessop -- 抱歉,这完全是废话。堆栈展开在 Java 中与在 C++ 中一样重要。
    • @sbi:无论这是否是回归,它都不是 Java 中存在异常的障碍。异常作为一种语言特性需要某种资源清理,它们不需要特别像 C++ 那样的资源清理。但是,如果没有正式的堆栈展开,程序员最终会想要finally,它以稍微不同的方式完成类似的工作。
    【解决方案4】:

    异常处理需要面向对象编程吗?

    没有。两者是正交的。其他人提到了 C 中用于处理错误的 setjmplongjmp。我想提一下 SEH。

    SEH(结构化异常处理)是微软对 C 的扩展,具有操作系统级别的支持。它可以让你编写类似 (example from MSDN) 的代码:

    __try 
    { 
        *pResult = dividend / divisor; 
    } 
    __except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ? 
             EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
    { 
        // handle exception
    }
    

    您也可以通过调用RaiseException 来引发您自己的异常。与setjmplongjmp 不同,您可以在__finally 块中进行自己的清理。事实上,C++ 异常是在 SEH 之上实现的(在 windows 上)。

    这是一个完全不面向对象的异常处理示例。

    另一个不使用任何面向对象特性的 C++ 示例:

    try {
        throw "Boom!";
    } catch(const char* str) {
        printf("Error: %s\n", str);
    }
    

    【讨论】:

    • @Steve314:我认为这不是真的。您需要从内部访问函数的堆栈框架,除了/最终做任何有用的事情,因此您需要编译器支持或在汇编中编写所有内容(包括函数体)。
    • 阅读更多内容后,我现在相信(但仍然不知道)SEH 的语言扩展和 API 是作为一个单元一起编写的。鉴于 the article I linked earlier says 的内容,操作系统服务似乎不太可能与语言扩展分开设计或使用。
    【解决方案5】:

    实现异常处理的非面向对象语言包括:

    • 东元科技
    • PL/1(多路)
    • C(正如几个人指出的那样,通过setjmp/longjmp
    • C by Unix 信号:Unix 内核信号源自 Multics 异常处理工具
    • 旧版本的 Lisp(当然,Common Lisp 允许 OOP,但在添加条件和重新启动时没有)显然从 ITS TECO 实现了条件和重新启动 (unwind-protects) — 根据 RMS (http:/ /www.gsim.aoyama.ac.jp/~ida/GNU/RMStalk1207.html),这意味着 Lisp 实际上是通过 Emacs 继承了异常处理(漂亮!)

    【讨论】:

    • 我正在阅读有关信号的信息,但一位消息人士提到信号并非用于异常处理。 en.wikibooks.org/wiki/C_Programming/Error_handling
    • 这是一个流程级的模拟。可以说既相似又不同。 可以通过使用signal 设置SIGFPE 处理程序来包装代码块,其方式与try {} catch (floating-point-error e) {} 大致相同。 en.wikipedia.org/wiki/… 似乎同意类比的想法。
    • 认为 setjmp/longjmp 甚至 Unix 信号是“异常机制”的说法有些牵强,甚至在 Ada 的时代也是如此。相反,Ada 风格的异常处理在很大程度上是对 Unix 信号等完全非结构化的方案的反应。
    • 它们都将控制权非本地转移到预先指定的入口点,该入口点专门设计用于处理一种中断正常代码流的异常情况。据我所知,剩下的就是基本概念之上的语法糖。
    • 好吧,如果你认为它是“语法糖”,异常处理错误处理代码位于设置异常处理程序的函数内部,因此可以访问所有 [至少,所有声明在 try 块的范围之外] 其局部变量(即需要清理的状态),而不会强迫您将该状态移动到堆栈以外的其他位置。
    【解决方案6】:

    对于一个非强制性的例子,试试 Haskell 的大小。异常甚至不需要内置到语言中;它们只是返回类型的一部分(例如,Either MyException MyValueExceptionalT IOException IO String)。可以使用 Control.Exception 模块中的 try 函数处理标准异常:

    main = do
      result <- try (evaluate (1 `div` 0))
      case result of
        Left  exception -> putStrLn $ "Caught: " ++ show exception
        Right value     -> putStrLn $ "Result: " ++ show value
    

    您还可以将函数用作带有catch 函数的异常处理程序,这里使用中缀:

    main = (print $ 1 `div` 0) `catch` \exception ->
      putStrLn $ "Caught: " ++ show exception
    

    您可以使用 Exception monad 在另一个 monad 中执行抛出异常的操作。通过处理所有可能的异常,您可以从 Exception monad 中逃脱。

    main =
       do result <- runExceptionalT someFunction
          case result of
             Exception exception -> putStrLn ("Caught: " ++ show exception)
             Success   value     -> putStrLn ("Result: " ++ show value)
    

    因为异常是函数类型签名的一部分,所以必须明确说明。这与 Java 中的受检异常本质上是一样的。

    【讨论】:

    • 不知道你是不是故意的,但你让它看起来有点好像仅仅使用返回类型Either 已经是异常处理,但事实并非如此。
    • @leftaroundabout:那是无意的。我补充了一点……虽然我不确定对于不熟悉 Haskell 的人是否会更清楚。
    【解决方案7】:

    异常处理已经存在了相当长的一段时间,远在 C++ 之前。几种“精品”语言很早就实现了异常处理,但 Ada(70 年代后期,IIRC)可能是最著名的。 Ada 有一丝 OO 特征,但从任何现代标准来看都不是 OO。

    异常处理也在几个版本的 PL/S 语言(绝对不是 OO)中实现,这些语言主要在 IBM 内部使用。早期的实现(可以追溯到 70 年代后期)是使用宏开发的(PS/S 宏处理器优于大多数),但后来的版本将 EH 嵌入到语言中。

    【讨论】:

      【解决方案8】:

      非 OO 语言也有例外,它是展开调用堆栈的有用抽象。例如ErlangForth

      【讨论】:

        【解决方案9】:

        好吧,在汇编语言中可以找到异常,您可以在其中使用陷阱(强制异常)和其他异常来控制程序的流程。一个例子是空指针或者为什么不是stackoverflow。 OO 语言的本质并没有支持异常处理。它们只是让它变得更容易(并且高级语言倾向于抛出更多异常)。异常是基本编程中的一个关键特性。我的意思不是所有的编程,我的意思是“常规”编程,包括汇编。当您说“在使异常起作用的机器代码中,C 缺少什么可以说,C++ 是什么?”时,我不明白您的意思。而且我不会说在 C 中捕获异常完全取决于程序员(但我是这方面的新手。如果有人可以纠正我,请这样做)。

        【讨论】:

          猜你喜欢
          • 2012-12-02
          • 2012-04-15
          • 2015-03-23
          • 2010-10-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多