【问题标题】:what happens when an unhandled exception is thrown from a constructor当构造函数抛出未处理的异常时会发生什么
【发布时间】:2012-12-21 01:14:37
【问题描述】:

当构造函数抛出未处理的异常时会发生什么?对于 Java 和 C++?会不会有内存泄露?

【问题讨论】:

  • 对于Java,程序不会崩溃吗?你的 IDE 可能甚至不允许你编译它。
  • 我认为在 Java 中您不必担心这一点,如果在 C++ 中可能发生这种情况,请使用 std::auto_ptr。
  • 关于 Java,您可能会在其他 SO 问题中阅读 Jon Skeet 的答案:stackoverflow.com/a/1371559/660990
  • “抛出未处理的异常”是什么意思?如何抛出已处理的异常?
  • 重要的是要意识到在 C++ 中,成员通常在构造函数抛出时被销毁。当然,在一个未捕获的异常之后程序结束,这使得这个问题相当学术。

标签: java c++ memory-leaks


【解决方案1】:

你问,

“当构造函数抛出未处理的异常时会发生什么?对于 Java 和 C++?会不会有内存泄露?”

未处理的异常是没有关联处理程序的异常。

在 C++ 中,任何未处理的异常都会终止程序。在这种情况下,未指定堆栈是否展开,即是否可以执行成功构造的局部变量的析构函数,具体取决于编译器。从哪里抛出异常(例如在构造函数内部)无关紧要。

C++11 §15.3/9
“如果没有找到匹配的处理程序,则调用函数std::terminate();在对 std::terminate() 的调用之前是否展开堆栈是实现定义的。”

Java 中未处理的异常同样必然会终止程序,如果不是主线程,至少会终止当前线程,但保证会调用 finally 子句:

Java SE 7 语言规范 §11.3
“如果找不到可以处理异常的catch子句,则终止当前线程(遇到异常的线程)。终止前,所有 finally 子句被执行 […]”

由于程序终止,实际上程序本身没有内存泄漏,因为实际上操作系统会在进程之后进行清理。

但是,崩溃的程序可能会在磁盘上留下临时文件,并且可能会从服务器进程泄漏其他资源,包括这些服务器进程中的内存泄漏。

【讨论】:

  • @anonymous downvoter:请解释您的反对意见,以便其他人可以从您的见解中学习。
  • @Cheers-and-hth-alf 否决了您帖子的原始版本,该版本非常简短,可以概括为“我不知道,我不知道,我不知道”;在我看来,这不是一个高质量的答案(看起来像是一个快速的帖子,可以在提出问题的实际答案之前踏入大门:困扰这个论坛的一种做法)。通过编辑和标准报价,它为我通过了标准。 Downvote 恢复了,它达到了它的目的。
  • @MikaelPersson:感谢您解释投票。最初我写道,我不知道是否处理了 Java final 块。之后,我添加了标准和规范报价,解决了该问题。因此,至少在您的情况下,该明确的陈述似乎使您认为 same 内容与现在是“不知道不知道不知道”的程度不同。我认为这意味着在 SO 上,我需要优先考虑不要将内容的感知转向毫无价值的方向,而不是引导对技术问题的看法远离过早的结论。 :-)
  • 调用 std::terminate 与终止程序并不完全相同。
【解决方案2】:

对于 Java:控制流返回给调用者,就像从常规方法抛出异常一样。没有内存泄漏(半构建的实例将被丢弃和垃圾收集)

【讨论】:

    【解决方案3】:

    好吧,至少在 C++ 中,未处理的异常会一直持续下去,直到它到达您的 main() 并因此关闭您的程序。未释放的内存将由操作系统处理。

    不确定这是否能回答您的问题...?

    所以,基本上,它就像是从任何其他函数中抛出的一样。

    【讨论】:

    • 在 c++ 中,就异常传播而言,不能保证异常会“继续下去”。但目前尚不清楚您是在谈论传播还是寻找处理程序。我认为因为不清楚而投反对票是不对的,所以我只是留下此评论,以便您可以修复您的答案。
    【解决方案4】:

    如果在构造函数中创建依赖对象,可能会发生内存泄漏。

    在任何语言/环境中,如果这些依赖项被不清理它们的外部实体引用,这可能会导致泄漏。

    在 JAVA 和 C# 中,如果未从外部引用依赖项,则不会导致泄漏。垃圾收集器最终会清理干净。

    在 C++ 中,如果未从外部引用依赖项,这肯定会导致泄漏。

    更多可能性请参见 Jon 的回答:Can constructors throw exceptions in Java?

    【讨论】:

      【解决方案5】:

      值得补充:

      1) Java 区分“已检查”和“未检查”异常

      2) 大多数用户定义的异常都应该被“检查”。这意味着代码甚至不会编译,除非调用链中的每个模块a)处理异常,或b)明确标记它可以“抛出”异常

      【讨论】:

      • 大多数异常都必须取消选中!
      • 来自Java documentation:“这是底线指南:如果可以合理地预期客户端可以从异常中恢复,则将其设为已检查异常。如果客户端无法从异常中恢复异常,使其成为未经检查的异常。”
      【解决方案6】:

      【讨论】:

      • 我记得,FAQ 没有涉及未处理异常的问题。但是,OP 的意思可能是询问“如果构造函数抛出异常会发生什么”,而不是“如果构造函数抛出未处理的异常会发生什么”。如果是这样,如果 OP 打算提出与所要求的完全不同的问题,那么您的回答可能很有价值。
      【解决方案7】:

      是否发生内存泄漏取决于代码的编写方式。如果您编写“好”代码,则不应出现内存泄漏。但是完全有可能想出严重错误的情况。

      如果构造函数在构造函数中分配了任何东西,那么事情很可能会出错。

      解决办法,一般来说就是使用所谓的“两阶段构造”,所以构造函数本身很简单,“不会出错”。构造对象后,您调用一个成员函数,该函数以可能失败的方式填充对象,然后您可以随意抛出异常,只要确保析构函数在某个点运行,一切都应该顺利进行。当心“析构函数中的部分构造对象” - 如果您的指针为 NULL,或者在析构函数的中途没有构造其他东西,会发生什么。

      [以上内容受制于“在我们回到 main 之前某处有一个处理程序,我们确实想做一些除了中止整个程序之外的事情”]。

      【讨论】:

      • -1 至少对于 c++ 两阶段构造不是答案,而是一个问题。这是一个众所周知的问题,我们在这里进行基础知识。在 bjarne stroustrup 的“c++ 编程语言”第 3 版的标准库中的标准库中的异常安全的附录中有一些讨论(虽然不完整),该附录可从 Bjarne 的网站以 PDF 格式获得。
      • 那么,在构造函数中抛出异常更好吗?当然,这两种解决方案都不好,但如果您必须抛出异常,请不要在构造函数中这样做。
      • 从构造函数中抛出可以确保周围没有不完整的对象。 c++ 创建机制支持这一点,这是所有质量代码处理构造失败的方式(包括标准库)。然而,对于 c++ 来说,它与手头的问题无关,因为“在构造函数中”是无关紧要的。 :-)
      • (在 C++ 中)两阶段构造是一个糟糕的想法——几乎整个语言和标准库都是围绕在构造函数中获取资源和在析构函数中释放资源的想法设计的。两阶段构造意味着对象存在“已构造但不可用”的值,这增加了必须考虑的状态量,并降低了类型安全性。坚持“使用仅管理单个资源的显式资源管理类来管理资源”的指导方针要好得多。
      【解决方案8】:

      C++ 或 Java 这两种语言的情况相似但又不同。

      当抛出异常时,它会向上传播堆栈,寻找处理程序。在 C++ 或 Java 中,它可能永远找不到一个,因此一直展开回到开始并终止程序。在 Java 中,有一个已检查异常的概念,它强制要求对异常进行某种处理(如果已检查)。在 C++ 中,有一个异常规范的概念,但它不切实际(设计不良)并且不应该使用,因此,将所有异常视为 C++ 中的“未检查”。

      无论异常最终终止程序还是被捕获到它被抛出的上游的某个地方,导致该异常的展开过程才是最重要的。如果它最终终止程序,那么在操作系统回收内存时当然不会出现内存泄漏。您需要担心的是:

      • 如果异常最终在上游某处处理,则在展开期间内存泄漏;并且,
      • 其他类型的可能泄漏的资源(例如,挂起的数据库操作、网络连接等),因为如果程序终止,它们将不会被操作系统回收/撤消。

      随着堆栈展开发生,在 C++ 中,简单地说,保证每个完全构造的堆栈绑定对象(包括正在构造的对象的数据成员或基类实例)将立即销毁(即,确定性地)以它们创建时的完全相反的顺序。因此,只要所有资源都直接与对象的构造/销毁(也称为“RAII”)相关联,在展开过程中就不会发生(内存或其他资源)泄漏,因为成功获取的每个资源都将被释放(除非在展开期间释放资源失败,您需要小心处理)。

      在 Java 中,“堆栈”展开以相同的方式发生,除了不是立即销毁对象,而是将它们标记为已丢弃(即被垃圾回收),并最终在未来某个不确定的时间点销毁.这保证没有内存泄漏,只要垃圾收集器保持足够长的时间来完成它的工作,我不确定如果程序最终以未处理的异常终止(但在那一点并不重要) . Java 的主要问题是其他资源。这些资源必须在finally 块中释放。 finally 块保证在展开期间被执行,但当然,它们必须包含释放在相应try 块中分配的资源的代码。只要程序员尽职尽责,资源就不会泄露。

      从构造函数抛出异常的事实并没有太大区别,基本规则与抛出异常时不泄漏资源的基本规则相同:

      • 在 C++ 中,将每个 single 资源(内存或其他)绑定到一个 single 对象,该语言保证其余部分不会泄漏。这就是资源获取即初始化 (RAII) 习语。
      • 在 Java 中,确保在其自己的 try-block 中写入每个 single 非内存资源获取,该 try-block 有自己的 finally-block 释放该 single 资源。

      在这两种情况下,您都必须干净利落地释放资源(不抛出)。

      【讨论】:

      • -1 “在 C++ 或 Java 中,它可能永远找不到一个,因此一直展开并终止程序”对于 C++ 是不正确的。 C++ 不保证未处理异常的堆栈展开。
      • 确实,这就是为什么你应该在 main 上有一个函数 try 块,捕获所有内容。这将保证堆栈展开,即使在没有它的系统上也是如此。
      猜你喜欢
      • 2020-12-03
      • 2011-03-25
      • 1970-01-01
      • 2011-08-18
      • 1970-01-01
      • 2010-12-12
      • 1970-01-01
      • 2010-09-09
      • 2012-04-30
      相关资源
      最近更新 更多