C++和Java中的异常处理有什么区别?

24
在Java中,如果特定行的代码导致程序崩溃,则异常将被捕获并且程序会继续执行。然而,在C++中,如果有一段引起程序崩溃的代码,比如:
try
{
    int x = 6;
    int *p = NULL;
    p = reinterpret_cast<int*>(x);

    *p = 10; // the program crashed here

    cout << "x = " << *p << endl;
}
catch(const char* Message)
{
    cout << "There is an run-time error";
}

然后程序仍会崩溃,异常也未被捕获。

那么C++中的异常处理有何意义?我是否理解有误?


17
异常和崩溃是两件不同的事情! - flight
我认为这会帮助你理解它[C++中的异常与Java中的异常]。 - Naved
1
你是否捕获了正确类型的异常? - c0da
7
空指针解引用是一种分段错误,它是一个信号而不是异常。 - Daniel
5个回答

34
这一行代码崩溃是因为对无效指针进行了解引用。在C++中,这不会引发异常,而是未定义的行为。
在C++中不存在空指针异常,不像Java会抛出空指针异常。相反,对无效指针进行解引用将导致未定义的行为。未定义的行为并不总是意味着崩溃,但如果崩溃了,那就是幸运的。

语言概述:

最后和RAII

C++和Java之间最显著的区别之一是Java支持finally语句。无论前面的catch块是否执行,都会执行finally块中的代码。例如:
try
{
}
catch (SomeException e)
{
}
finally
{
  //code here is always exectued.
}

finally语句的目的是允许程序员在该点进行清理,即释放套接字、关闭文件句柄等等。尽管Java运行垃圾收集器,但垃圾收集只适用于内存,而不适用于其他资源。仍然有时需要手动处理资源。现在C++没有finally语句,因此建议使用该语言的用户遵守RAII原则(资源获取即初始化)。Stroustrup在这里对其进行了解释:http://www.stroustrup.com/bs_faq2.html#finally。我更喜欢称其为资源销毁即释放,但基本上当您的对象超出范围并调用析构函数时,该析构函数应释放对象维护的任何资源。

例如,C++11x提供了std::unique_ptr来管理这个问题:

void foo()
{
  std::unique_ptr<T> t(new T)
  try
  {
    //code that uses t
  }
  catch (...)
  {
  }
}

使用new分配的资源将在函数结束时被删除。

捕获所有语句

因为Java中的所有异常都继承自一个共同的基类Exception,如果你想让你的catch子句捕获任何异常,那么设置它就像这样:

catch (Exception e)
{
  //any exception thrown will land here.
}

C++中没有限制可以抛出什么异常,也没有所有异常的共同基类。标准惯例是通过继承std::exception来形成自定义的异常类,但语言不强制执行此操作。相反,有一个特殊的语法可以捕获所有异常:

catch (...)
{

}

未处理的异常

这是另一个语言行为不同的领域。在C++中,未被捕获的抛出异常将调用std::terminate。std::terminate的默认行为是调用abort,生成SIGABRT并停止整个程序。

在Java中,行为是打印堆栈跟踪并终止发生未捕获异常的线程。但是,由于Java程序员可能提供了UncaughtException处理程序,因此行为可能与终止线程的默认行为有所不同。


1
我认为这个答案几乎完美,所以我不会加入我的东西,但你可能想编辑它来涉及Java(每个线程/线程组)和C++(运行时的一部分)默认异常处理程序之间的差异,以及在Java中一个进程通常是多线程的,未处理的异常只会使一个线程崩溃。 - ddimitrov
@ddimitrov:是的,可能还有更多要添加的。 - sashang
5
“C++没有finally,所以建议用户使用RAII”。->“Java没有析构函数,因此他们必须在每个地方编写finallyfinally是手动内联析构函数的一种形式。在最好的情况下会导致代码重复,如果忽略某些情况则会造成资源泄漏。” - MSalters
3
“有时候,做任何事都需要合适的时间和地点。虽然所有使用“finally”的情况都可以通过在块的开头创建一个对象来表达,该对象的目的是在块退出时运行“finally”代码,但在许多情况下,为此目的创建包装器对象可能不如将适当的代码放入“finally”块清晰明了。” - supercat

8

并非所有的崩溃都是由于未处理的异常引起的。以您的示例为例,C++标准规定对空指针进行解引用会导致未定义行为。在Windows中,您可以使用结构化异常处理(SEH)来处理程序崩溃问题,而不需要抛出C++异常:__try/__except/__finally。在Unix中,您可以设置特殊的信号处理程序。


此外,您的代码存在错误。对于const char *的异常处理程序仅在抛出此类型的异常时才会被调用。对于标准异常,您应该捕获std::exception或其适当的子类。要捕获任何C++异常,请使用catch (...)

1
在微软领域中,经常不建议使用catch(...)。它会捕获所有结构化异常,例如访问冲突。这可能掩盖了严重的问题。 - seand
@seand,说得好。在Java中捕获Exception通常也是不好的编程风格。 - Don Reba

1
实际上,在C++中,您是可以捕获系统异常的。有一个编译器选项(至少在Visual Studio中),可以让您捕获访问冲突异常(这就是为什么您的程序崩溃的原因)。
Java更加谨慎,因此更显复杂。
请思考以下内容:
在Java中:
int x[10];
int i = 20;
try
{
    int k = x[i];
}
catch (ArrayIndexOutOfBoundsException ex)
{
     //will enter here
}

Int C++:

int x[10];
int i = 20;
try
{
    if ( i >= 10 )
        throw new "Index out of bounds";
    int k = x[i];
}
catch (...)
{
    //now it will enter here
}

这一切都与你是否想要将更多的事情留给运行时环境有关(就像Java的情况一样),还是你自己想要处理这些事情。C++给你更多的控制权,但你必须更加注意。

如果异常没有被处理,你的Java程序也会崩溃 - 想想看,如果一个方法明确地抛出一个异常,你不能不处理它,因为编译器不允许你这样做。如果没有明确地处理,除非被try/catch包围,否则你的程序仍然会崩溃。

如果你问为什么C++不能处理系统异常,我已经回答了:它们可以处理,只是默认情况下这个功能被关闭了。


据我所知,Visual Studios是唯一提供此扩展的编译器。总的来说,这不是你想要的东西(虽然有例外情况,并且在少数情况下它是合适的,但最好有它可用);编程错误通常意味着您不再知道程序的内部状态,因此应尽快终止(而不是尝试执行析构函数)。 - James Kanze
@JamesKanze "如果一个方法明确地抛出异常,你就不能不处理它,因为编译器不允许". 这只适用于已检查的异常。在Java中,从RuntimeException派生的类型的实例(它们又从Exception和Throwable派生)可以被抛出而不必在throws子句中声明;NullPointerException是一个完美的例子。 - user

1
在任何语言中,异常的目的是处理异常情况。但仍有一些情况,你可以对全局程序状态做出合理的假设,并可能进行恢复。编程错误通常意味着你不能对全局程序状态做出任何假设,并且必须尽快终止代码的执行,执行最少的附加代码(因为你不知道它会做什么)。
在Java中,几乎所有的问题都通过异常报告。从常见的预期错误(尝试打开文件时“找不到文件”)到关键的内部错误(java.lang.VirtualMachineError)。C++给你选择:如果你检测到编码错误(断言失败),你可以立即终止进程(通常比在未知状态下继续更合适);如果“错误”是通常会在日常操作中发生的事情(“找不到文件”),你可以测试状态或使用返回代码(通常比异常更合适)。对于两者之间的许多情况(例如内存不足),C++使用异常。

在特定的应用程序中,最合适的当然是各不相同的:有些情况下,“文件未找到”是异常的(例如,如果文件是应用程序的一部分,则无法运行),并且需要引发异常。同样,在特定情况下,空指针可以用于控制程序逻辑(if ( ptr == NULL ) ...)或对应于异常情况(if ( ptr == NULL ) throw ...);在其他情况下,空指针是编程错误(assert( ptr != NULL))。


0
  1. C++和Java都有try和catch块,但Java有一个新的块finally,在try和catch之后总是被执行。
  2. Java只抛出对象,而C++抛出数据(原始数据、指针和对象)。
  3. C++有catch(...) {}来捕获所有类型的异常,而Java有catch(Exception e) {}来捕获所有异常。

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