# 一、概述

C++ 异常机制是一种用于处理程序运行时错误和异常情况的机制。它允许在发生异常时抛出异常对象(通过 throw 语句),并在适当的地方捕获并处理这些异常(通过 try-catch 块)。

异常的基本流程如下:

  1. throw 语句:当程序运行到某个错误或异常的情况时,可以使用 throw 语句抛出一个异常对象。异常对象可以是内置类型、自定义类型或标准库中的异常类。
  2. try-catch 块:使用 try 关键字开始一个代码块,该代码块可能会抛出异常。在 try 块中,程序会监视是否有异常被抛出,如果有,就会跳到与之匹配的 catch 块。
  3. catch 块: catch 块用于捕获并处理异常。它指定了需要捕获的异常类型,并在发生异常时执行相应的处理逻辑。可以有多个 catch 块,每个 catch 块捕获不同类型的异常。

下面是一个具体的示例,展示了如何抛出和捕获异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>

void divideNumbers(int numerator, int denominator) {
if (denominator == 0) {
throw std::runtime_error("Divide by zero exception");
}

int result = numerator / denominator;
std::cout << "Result: " << result << std::endl;
}

int main() {
try {
int numerator, denominator;
std::cout << "Enter numerator: ";
std::cin >> numerator;
std::cout << "Enter denominator: ";
std::cin >> denominator;

divideNumbers(numerator, denominator);
} catch (const std::exception& ex) {
std::cout << "Exception caught: " << ex.what() << std::endl;
}

return 0;
}

在上述代码中, divideNumbers 函数用于计算两个数的除法。如果分母为零,将抛出一个 std::runtime_error 异常,并带有自定义的错误消息。

main 函数中,我们使用 try-catch 块来捕获可能抛出的异常。如果 divideNumbers 函数抛出异常,将跳转到匹配的 catch 块。在本例中,我们使用 const std::exception& 引用来捕获任何类型的异常,并使用 ex.what() 获取异常对象的错误消息。

如果用户在输入分母时输入了零,将会触发异常,然后程序将跳转到 catch 块,并输出相应的错误消息。

这个示例展示了如何使用 throw 语句抛出异常,并在 try-catch 块中捕获和处理异常。通过适当地处理异常,我们可以改善程序的健壮性和容错性。

# 二、异常类

补充说明: 在 C++ 中,可以抛出不同类型的异常,包括标准库中提供的异常类,也可以自定义异常类来满足特定需求。通常,异常类应该从 std::exception 类派生,以便能够通过基类的引用或指针进行统一的异常捕获。

自定义异常类的示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>

class MyException : public std::exception {
public:
MyException(const std::string& message) : message_(message) {}

const char* what() const noexcept override {
return message_.c_str();
}

private:
std::string message_;
};

void processInput(int value) {
if (value < 0) {
throw MyException("Negative value exception");
}

std::cout << "Value: " << value << std::endl;
}

int main() {
try {
int value;
std::cout << "Enter a positive value: ";
std::cin >> value;

processInput(value);
} catch (const std::exception& ex) {
std::cout << "Exception caught: " << ex.what() << std::endl;
}

return 0;
}

在上述代码中,我们自定义了一个名为 MyException 的异常类,它从 std::exception 派生。我们在 processInput 函数中检查输入值,如果值为负数,将抛出 MyException 异常,并传递自定义的错误消息。

main 函数中,我们使用 try-catch 块来捕获可能抛出的异常。如果 processInput 函数抛出 MyException 异常,将跳转到匹配的 catch 块,并输出相应的错误消息。

# 三、异常的好处

C++ 的异常机制提供了一种用于处理异常情况的结构化方式,具有以下几个好处:

  1. 分离正常流程和异常处理逻辑:异常机制允许我们将正常的业务逻辑与异常处理逻辑分离开来,使代码更加清晰和易于维护。我们可以专注于编写正常的代码,而将异常处理部分留给专门的异常处理程序。
  2. 异常处理的集中性:通过异常机制,我们可以将异常处理逻辑集中到一个或几个地方,提高代码的可读性和可维护性。这样,在代码中的每个可能引发异常的地方,我们不需要重复编写相同的异常处理逻辑,而是通过捕获异常并在适当的位置进行处理。
  3. 提供更灵活的错误处理:使用异常机制,我们可以将错误信息传递到适当的异常处理程序中,而不是通过错误码或返回值来传递。这样可以提供更多的上下文信息,并且可以根据具体情况采取不同的处理措施。
  4. 避免混淆错误处理和业务逻辑:使用条件语句来处理错误可能导致代码变得冗长和复杂,因为我们需要在每个可能引发异常的地方进行条件检查。 这样会使代码逻辑变得混乱,难以理解和维护。而异常机制能够将错误处理与业务逻辑分离,提高代码的可读性和可维护性。
  5. 异常的传播性:异常机制提供了异常在调用栈中传播的能力,即在函数调用链的各个层级中,异常可以被捕获和处理。这样可以避免在每个函数中都进行异常处理,只需要在适当的地方进行捕获和处理即可。(可以向上传播)

需要注意的是,异常机制并不适合处理所有类型的错误。对于某些可预测的错误或边界条件,使用条件语句来进行处理可能更加合适。异常应该用于表示意外情况、运行时错误或无法处理的情况。

总结起来,C++ 的异常机制提供了一种结构化的、集中处理异常的方式,使得代码更清晰、更易于维护。它分离了正常流程和异常处理逻辑,并提供了更灵活的错误处理方式,避免了混淆错误处理和业务逻辑。然而,在使用异常机制时,需要合理使用,根据具体情况选择使用异常还是条件语句处理错误。

# 四、异常与条件语句的区别

举例说明:
当处理文件读取异常时,让我们来比较使用异常机制和条件语句的处理方式。假设我们需要读取一个文件的内容,并输出文件的内容到控制台。

使用异常机制的示例代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <fstream>

void readFile(const std::string& filename) {
std::ifstream file(filename);

if(!file) {
throw std::runtime_error("Failed to open file: " + filename);
}

std::string line;
while(std::getline(file, line)) {
std::cout << line << std::endl;
} file.close();
}

int main() {
std::string filename = "example.txt";

try {
readFile(filename);
} catch (const std::exception& e) {
std::cerr << "Exception occurred: " << e.what() << std::endl;
}

return 0;
}

在上述代码中, readFile 函数尝试打开文件并读取其内容。如果文件打开失败,将抛出 std::runtime_error 异常。在 main 函数中,我们使用 try-catch 块来捕获可能抛出的异常,并在异常发生时进行处理。异常处理程序将打印异常信息到标准错误流。

现在,我们来看看使用条件语句的处理方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <fstream>

void readFile(const std::string& filename) {
std::ifstream file(filename);

if(file) {
std::string line;
while(std::getline(file, line)) {
std::cout << line << std::endl;
} file.close();
} else {
std::cerr << "Failed to open file: " << filename << std::endl;
}
}

int main() {
std::string filename = "example.txt";
readFile(filename);
return 0;
}

在这个示例中, readFile 函数尝试打开文件并读取其内容。如果文件打开成功,则将文件内容输出到控制台;如果文件打开失败,则直接在函数内部使用条件语句输出错误信息到标准错误流。

通过对比这两个示例,我们可以看到使用异常机制的好处:

  1. 代码清晰度:使用异常机制可以更清晰地将正常流程和异常处理逻辑分离。异常处理程序专门处理异常情况,而正常流程部分保持简洁
  2. 代码复用:异常处理程序位于 main 函数中的 try-catch 块中,可以在整个程序中重复使用,而不需要在每个可能引发异常的函数中编写重复的错误处理代码。
  3. 异常传播性:如果在 readFile 函数中发生异常,它将在调用栈中传播,直到被 main 函数中的 try-catch 块捕获。这样,我们可以在适当的地方集中处理异常,而不需要在每个函数中进行条件检查。

总结来说,异常机制提供了一种结构化的、集中处理异常的方式,使得代码更加清晰和易于维护。它分离了正常流程和异常处理逻辑,并提供了更灵活的错误处理方式。通过示例代码的对比,我们可以更好地理解使用异常机制的好处。

需要注意的是,在实际编码中,我们应该根据具体情况选择合适的错误处理方式。有些情况下,使用条件语句进行错误处理可能更加简洁和合适。异常机制应该用于表示意外情况、运行时错误或无法处理的情况。

# 五、异常类型

当使用 C++ 异常机制时,我们可以使用各种不同的异常类型来表示不同的错误或异常情况。C++ 标准库提供了一些常见的异常类型,同时我们也可以自定义异常类型来满足特定的需求。

下面是一些常见的 C++ 异常类型及其使用场景:

  1. std::exceptionstd::exception 是 C++ 异常类的基类,用于表示通用的异常情况。它提供了一个 what() 成员函数,返回异常的描述信息。
  2. std::runtime_errorstd::runtime_error 表示在运行时发生的错误,例如无效的参数、资源不可用等。它是从 std::exception 派生而来的,通常用于表示逻辑错误或不可恢复的错误。
  3. std::logic_errorstd::logic_error 表示逻辑错误,例如在代码中违反了逻辑规则、使用了无效的状态等。它也是从 std::exception 派生而来的,通常用于表示可预测且可修复的错误。
  4. std::invalid_argumentstd::invalid_argument 表示传递给函数的参数无效。例如,当函数要求一个有效的参数值,并且传递了一个无效值时,可以抛出该异常。
  5. std::out_of_rangestd::out_of_range 表示索引或迭代器超出了有效范围。例如,在数组、容器或字符串中使用无效的索引时可以抛出该异常。
  6. std::bad_allocstd::bad_alloc 表示内存分配失败。当动态内存分配操作(如 newmalloc )无法满足请求时,可以抛出该异常。

除了上述标准库提供的异常类型,我们还可以自定义异常类型来满足特定的需求。自定义异常类型可以从 std::exception 或其他标准库提供的异常类型派生,以便更好地表示特定的错误或异常情况。

使用适当的异常类型可以增加代码的可读性和可维护性,并提供有关错误或异常的更多上下文信息。当捕获异常时,我们可以根据异常类型进行精确的处理或采取适当的措施,以便正确地处理错误情况。

# 六、异常处理

如果您在代码中抛出了异常却没有相应的异常处理(没有进行 catch 操作),那么异常将会沿着调用栈向上传播,直到遇到能够捕获并处理该异常的地方,或者在程序的顶层时导致程序终止。

具体来说,当一个异常被抛出时,程序会搜索当前的执行上下文(函数调用栈)以查找能够处理该异常的 catch 语句块。如果找不到匹配的 catch 块,异常就会沿着调用栈向上传播,继续搜索调用栈中的下一个执行上下文,直到找到合适的 catch 块或者达到程序的顶层。

如果异常一直没有被捕获,而且在程序的顶层也没有合适的 catch 块来处理该异常,那么程序就会终止运行,并输出一个未捕获异常的错误信息。这通常会包括异常类型、异常发生的位置和相关的调用栈信息,以帮助定位问题。

因此,在编写代码时,建议捕获可能抛出的异常,并进行相应的处理操作,以便更好地控制程序的行为和错误处理。这样可以提高程序的健壮性和可靠性,避免未处理的异常导致程序异常终止。

需要注意的是,在某些特殊情况下,比如在应用程序的顶层或线程的入口点,可以使用类似 try-catch 的结构来捕获并处理未处理的异常,以便进行适当的日志记录和资源清理。

总之,如果抛出的异常没有被捕获,它将沿着调用栈向上传播,直到找到合适的 catch 块或导致程序终止。因此,合理地捕获和处理异常是确保程序稳定性和可靠性的重要步骤。

更新于

请我喝杯咖啡吧~

Rick 微信支付

微信支付

Rick 支付宝

支付宝