# C++中的异常处理机制
面试时没有回答好问题是很正常的,被面试官质疑时,要有今天不会明天会的底气,面后做好复盘,不在同一道题目上跌倒第二次,今天就从 C++中的异常处理机制 背起来
# 简要回答
C++异常处理基于try-throw-catch机制,提供结构化的错误处理方式。
try块定义监控范围,throw抛出异常对象,catch捕获并处理异常。
支持异常类型匹配、栈展开和资源自动清理,通过RAII保证异常安全。
# 详细回答
C++异常处理核心组件:
1.异常抛出(throw),使用throw表达式抛出异常对象),可以抛出任意类型的对象(内置类型、自定义类、标准异常等),抛出异常会立即终止当前函数执行
2.异常捕获(catch),catch块按顺序匹配异常类型,支持异常类型继承层次中的多态捕获,可以使用catch(...)捕获所有异常
3.栈展开(Stack Unwinding),从抛出点开始向上查找匹配的catch块,自动调用局部对象的析构函数,保证资源正确释放
4.异常规格(Exception Specifications),C++11引入noexcept说明符,替代旧的动态异常规格,提供编译期异常保证
# 代码示例
基本的异常处理示例
#include <iostream>
#include <stdexcept>
#include <string>
// 自定义异常类
class MyException : public std::exception {
private:
std::string message;
public:
MyException(const std::string& msg) : message(msg) {}
const char* what() const noexcept override {
return message.c_str();
}
};
double divide(double a, double b) {
if (b == 0) {
throw std::invalid_argument("Division by zero!");
}
return a / b;
}
void process_data(int value) {
if (value < 0) {
throw MyException("Negative value not allowed: " + std::to_string(value));
}
if (value > 100) {
throw std::out_of_range("Value too large: " + std::to_string(value));
}
std::cout << "Processing value: " << value << std::endl;
}
void basic_exception_demo() {
try {
std::cout << "10 / 2 = " << divide(10, 2) << std::endl;
std::cout << "10 / 0 = " << divide(10, 0) << std::endl; // 这里会抛出异常
}
catch (const std::invalid_argument& e) {
std::cout << "Caught invalid_argument: " << e.what() << std::endl;
}
try {
process_data(50); // 正常
process_data(-5); // 抛出MyException
process_data(200); // 不会执行到这里
}
catch (const MyException& e) {
std::cout << "Caught MyException: " << e.what() << std::endl;
}
catch (const std::exception& e) {
std::cout << "Caught std::exception: " << e.what() << std::endl;
}
catch (...) {
std::cout << "Caught unknown exception" << std::endl;
}
}
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
文件管理中的RAII noexcept和异常的安全保证
#include <iostream>
#include <vector>
class NoExceptDemo {
public:
// 基本不抛出异常的函数
int get_value() const noexcept {
return 42;
}
// 可能抛出异常的函数
void risky_operation() {
throw std::runtime_error("This might fail");
}
// 移动构造函数通常标记为noexcept
NoExceptDemo(NoExceptDemo&& other) noexcept {
std::cout << "Move constructor (noexcept)" << std::endl;
}
// 析构函数不应该抛出异常
~NoExceptDemo() noexcept {
std::cout << "Destructor (noexcept)" << std::endl;
}
};
void noexcept_usage() {
NoExceptDemo demo;
// noexcept运算符检查表达式是否可能抛出异常
std::cout << "get_value is noexcept: " << noexcept(demo.get_value()) << std::endl;
std::cout << "risky_operation is noexcept: " << noexcept(demo.risky_operation()) << std::endl;
// 在vector重新分配时,noexcept移动构造函数更高效
std::vector<NoExceptDemo> items;
items.push_back(NoExceptDemo()); // 优先使用移动构造
}
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
36
37
# 知识拓展
- 知识图解

- 适用场景
不可恢复错误 - 内存分配失败、硬件错误等
构造函数失败 - 对象构造无法完成时
资源获取失败 - 文件打开、网络连接等
逻辑错误 - 违反前置条件、无效参数等
跨多层调用错误处理 - 错误需要向上传递多级时
- 面试官很能追问
Q1: 异常处理相比返回错误码有什么优势?
A1:分离错误处理:正常逻辑和错误处理代码分离,提高可读性
自动传播:异常自动向上传播,无需每层检查错误码
类型安全:异常是类型安全的,编译器可以检查类型匹配
无法忽略:未处理的异常会导致程序终止,错误不会被静默忽略
构造函数支持:构造函数无法返回错误码,只能使用异常报告失败
Q2: 什么是栈展开?在异常处理中起什么作用?
A2: 栈展开是从异常抛出点开始,沿着调用链向上查找匹配catch块的过程。在这个过程中:
自动调用局部对象的析构函数
释放自动存储期的资源
维护调用栈的完整性
保证资源不泄漏(结合RAII) 栈展开确保了即使发生异常,程序也能正确地清理资源。
Q3: 为什么应该按引用捕获异常?
A3: 按引用捕获异常有多个优势:
避免切片:捕获派生类异常时保持多态性
避免拷贝:不需要复制异常对象,提高性能
保持原始异常:能够重新抛出原始异常对象
一致性:与标准库异常处理方式一致
Q4: 在析构函数中抛出异常会有什么问题?
A4:在析构函数中抛出异常是危险的,因为:
双重异常:如果栈展开过程中析构函数抛出异常,会立即调用terminate
资源泄漏:可能中断其他资源的清理过程
未定义行为:C++标准规定这种情况会导致程序终止
最佳实践:析构函数应该用noexcept声明,并吞掉所有异常
评论
验证登录状态...