当构造函数抛出未处理的异常时会发生什么?对于 Java 和 C++?会不会有内存泄漏?
8 回答
你问,
“当构造函数抛出未处理的异常时会发生什么?对于 Java 和 C++?会不会有内存泄露?”
未处理的异常是没有关联处理程序的异常。
在 C++ 中,任何未处理的异常都会终止程序。在这种情况下堆栈是否被展开是未指定的,即成功构造的局部变量的析构函数可以根据编译器执行或不执行。从哪里抛出异常(例如在构造函数内部)是无关紧要的。
C++11 §15.3/9:
“如果没有找到匹配的处理程序,std::terminate()
则调用该函数;在此调用之前是否展开堆栈是std::terminate()
实现定义的。”
Java中未处理的异常同样必然会终止程序,或者至少是当前线程,如果它不是主线程,但有保证的finally
子句调用:
Java SE 7 语言规范 §11.3:
“如果找不到catch
可以处理异常的子句,则终止当前线程(遇到异常的线程)。在终止之前,所有finally
条款都被执行 […]”
由于程序终止,实际上程序本身没有内存泄漏,因为实际上操作系统会在进程之后进行清理。
但是,崩溃的程序可能会在磁盘上留下临时文件,并且可能会从服务器进程泄漏其他资源,包括这些服务器进程中的内存泄漏。
对于 Java:控制流返回给调用者,就像从常规方法抛出异常一样。没有内存泄漏(半构建的实例将被丢弃和垃圾收集)
好吧,至少在 C++ 中,一个未处理的异常会一直持续下去,直到它到达你的 main() 并因此关闭你的程序。未释放的内存将由操作系统处理。
不确定这是否回答了您的问题...?
所以,基本上,就像它是从任何其他函数中抛出的一样。
如果在构造函数中创建依赖对象,可能会发生内存泄漏。
在任何语言/环境中,如果这些依赖项被不清理它们的外部实体引用,这可能会导致泄漏。
在 JAVA 和 C# 中,如果未从外部引用依赖项,则不会导致泄漏。垃圾收集器最终会清理干净。
在 C++ 中,如果未从外部引用依赖项,这肯定会导致泄漏。
有关更多可能性,请参见 Jon 的回答:Can constructors throw exceptions in Java?
值得补充的是:
1) Java 区分“已检查”和“未检查”异常
2)大多数用户定义的异常应该被“检查”。这意味着代码甚至不会编译,除非调用链中的每个模块 a) 处理异常,或 b) 明确标记它可以“抛出”异常
两种语言(C++ 或 Java)的情况相似但又有所不同。
当抛出异常时,它会向上传播堆栈,寻找处理程序。在 C++ 或 Java 中,它可能永远找不到一个,因此一直展开回到开始并终止程序。在 Java 中,有一个已检查异常的概念,它强制要求对异常进行某种处理(如果已检查)。在 C++ 中,有一个异常规范的概念,但它不切实际(设计不良)并且不应该使用,因此,在 C++ 中将所有异常视为“未检查”。
无论异常最终终止程序还是被捕获到它被抛出的上游的某个地方,导致该异常的展开过程都是重要的。如果它最终终止程序,那么在操作系统回收内存时当然不会出现内存泄漏。你需要担心的是:
- 如果异常最终在上游某处处理,则展开期间的内存泄漏;和,
- 其他类型的可能泄漏的资源(例如,挂起的数据库操作、网络连接等),因为如果程序终止,它们将不会被操作系统回收/撤消。
当堆栈展开发生时,在 C++ 中,简单地说,保证每个完全构造的堆栈绑定对象(包括正在构造的对象的数据成员或基类实例)将立即销毁(即确定性地)与创建它们的顺序完全相反。因此,只要所有资源都直接与对象的构造/销毁(也称为“RAII”)相关联,在展开过程中就不会发生(内存或其他资源)泄漏,因为成功获取的每个资源都将被释放(除非在展开期间释放资源失败,这是您需要小心处理的事情)。
在 Java 中,“堆栈”展开以相同的方式发生,只是不是立即销毁对象,而是将它们标记为已丢弃(即被垃圾回收),并最终在未来某个不确定的时间点销毁。这保证没有内存泄漏,只要垃圾收集器保持足够长的时间来完成它的工作,我不确定如果程序最终终止于未处理的异常(但此时无关紧要) . Java 的主要问题是其他资源。这些资源必须在finally
块中释放。这些finally
块保证在展开期间被执行,但是,当然,它们必须包含代码来释放相应的分配的资源try
堵塞。只要程序员做他的工作,资源就不会泄漏。
从构造函数抛出异常的事实并没有太大区别,基本规则与抛出异常时不泄漏资源的基本规则相同:
- 在 C++ 中,将每一个资源(内存或其他)绑定到一个对象,语言保证其余的,没有泄漏。这是资源获取即初始化 (RAII) 习语。
- 在 Java 中,确保在其自己的 try-block 中写入每个非内存资源获取,该 try-block 具有自己的 finally-block 释放该单个资源。
在这两种情况下,您都必须干净地释放资源(不抛出)。
是否发生内存泄漏取决于代码的编写方式。如果您编写“好”代码,则不应出现内存泄漏。但是完全有可能想出严重错误的情况。
如果构造函数在构造函数中分配了任何东西,那么事情很可能会出错。
解决方法,一般来说就是使用所谓的“两阶段构造”,这样构造器本身就很简单,“不会出错”。构造对象后,您调用一个成员函数,该函数以可能失败的方式填充对象,然后您可以随意抛出异常,只要确保析构函数在某个点运行,一切都应该顺利进行。当心“析构函数中的部分构造对象” - 如果您的指针为 NULL,或者在析构函数的中途没有构造其他东西,会发生什么。
[以上内容受制于“在我们回到 main 之前某处有一个处理程序,并且我们确实想要做一些除了中止整个程序之外的事情”]。