C++ try-catch 异常处理机制详解
一、核心概念与基本语法:
C++ 的
try-catch
异常处理机制是一种用于捕获和处理程序运行时错误的结构化方式,它允许程序在错误发生时 "抛出" 异常,并在合适的位置 "捕获" 并处理异常,避免程序直接崩溃,同时使错误处理逻辑与正常业务逻辑分离,提高代码可读性。异常处理的核心由三个关键字构成:
try
、throw
、catch
,三者协同工作:
try
:标记可能抛出异常的代码块("监控区")。throw
:在错误发生时 "抛出" 异常(可以是任意类型的值或对象)。catch
:紧跟在try
后,用于 "捕获" 并处理特定类型的异常("处理区")。语法结构:
try {// 可能抛出异常的代码块throw SomeException("错误信息"); // 主动抛出异常 } catch (const SpecificException& e) { // 捕获特定异常(推荐const引用)std::cer<< "特定异常: " << e.what() << std::endl; } catch (const std::exception& e) { // 捕获标准异常基类std::cerr << "标准异常: " << e.what() << std::endl; } catch (...) { // 捕获所有未处理的异常std::cerr << "未知异常" << std::endl; }
二、关键机制详解
1.
throw
:抛出异常
throw
用于在检测到错误时主动触发异常处理流程,其语法为throw 表达式;
,表达式的结果即为 "异常值",类型可以是:
- 基本类型(如
int
、double
、const char*
等);- 自定义类型(如结构体、类对象等,推荐用类对象携带更多错误信息)。
示例:
void divide(int a, int b) {if (b == 0) {throw "除数不能为0"; // 抛出字符串常量(const char*类型)// 也可以抛出int:throw -1; 或自定义对象:throw DivideError("除数为0");}cout << "结果:" << a / b << endl; }
2.
try
块:监控异常
try
块包裹 "可能抛出异常的代码",程序执行到try
块时,会监控其中的代码:
- 如果代码正常执行(未抛出异常),
try
块执行完毕后,跳过所有catch
块,继续执行后续代码。- 如果代码中
throw
了异常,try
块会立即终止执行,进入 "异常匹配" 阶段:寻找第一个能处理该异常类型的catch
块。3.
catch
块:捕获并处理异常
catch
块必须紧跟try
块,用于匹配并处理特定类型的异常,其匹配规则为:catch
的参数类型必须与throw
抛出的异常类型完全匹配(或存在可兼容的隐式转换)。
- 若匹配成功:执行该
catch
块的处理逻辑,执行完毕后,跳过后续catch
块,继续执行整个try-catch
结构之后的代码。- 若所有
catch
块都不匹配:异常会 "向上传播"多
catch
块的顺序问题:如果有多个catch
块,更具体的类型必须放在前面,否则会被更通用的类型 "提前捕获"。例如:派生类异常应放在基类异常之前(因为派生类可隐式转换为基类)。try {// ... 可能抛出异常 } catch (Derived& e) { // 派生类异常(更具体),放前面// 处理派生类异常 } catch (Base& e) { // 基类异常(更通用),放后面// 处理基类异常 }
4. 异常的传播与终止
如果
try
块中抛出的异常在当前的catch
块中未被匹配,异常会沿着 "函数调用栈" 向上传播:
- 退出当前函数,回到调用该函数的位置,检查调用位置是否有
try-catch
结构,继续寻找匹配的catch
块。- 若一直传播到程序入口(
main
函数)仍未被捕获,则程序会调用std::terminate()
终止运行(默认行为是直接崩溃)。5. 栈展开(Stack Unwinding)
当异常被抛出且未在当前
try
块中捕获时,程序会自动执行 "栈展开":
- 销毁当前
try
块中创建的所有局部对象(调用其析构函数)。- 退出当前函数,销毁函数内的局部对象,继续向上传播。
这一机制保证了异常发生时,已分配的资源(如内存、文件句柄等)能被正确释放,是 C++ RAII(资源获取即初始化)思想的重要支撑(例如智能指针
std::unique_ptr
利用栈展开自动释放内存)。
案例分析:
案例1:文件操作异常处理
#include <fstream> #include <stdexcept>void readFile(const std::string& filename) {std::ifstream file(filename);if (!file) {throw std::runtime_error("无法打开文件: " + filename);}// 文件读取操作... }int main() {try {readFile("nonexistent.txt");}catch (const std::exception& e) {std::cout << "[文件系统错误] " << e.what() << std::endl;}return 0; }
案例2:数值计算安全校验
double safeDivide(double a, double b) {if (b == 0) throw std::invalid_argument("除数不能为零");return a / b; }// 使用示例 try {std::cout << safeDivide(10, 0); } catch (const std::invalid_argument& e) {std::cerr << "数学错误: " << e.what() << std::endl; }
3. 高级用法
自定义异常类(继承体系)
实际开发中,通常使用自定义异常类(而非基本类型)来携带更丰富的错误信息(如错误码、描述、位置等),且建议继承标准库的
std::exception
(便于统一处理)。#include <iostream> #include <string> #include <exception>// 自定义异常基类 class DatabaseException : public std::exception { private:std::string message;int errorCode;public:DatabaseException(const std::string& msg, int code = 0) : message(msg), errorCode(code) {}virtual const char* what() const noexcept override {return message.c_str();}int getErrorCode() const { return errorCode; }virtual ~DatabaseException() = default; };// 具体的数据库异常类 class ConnectionException : public DatabaseException { public:ConnectionException(const std::string& msg, int code = 1001): DatabaseException(msg, code) {} };class QueryException : public DatabaseException { public:QueryException(const std::string& msg, int code = 1002): DatabaseException(msg, code) {} };class Database { public:void connect() {// 模拟连接失败throw ConnectionException("无法连接到数据库服务器", 1001);}void executeQuery(const std::string& query) {if (query.empty()) {throw QueryException("查询语句不能为空", 1002);}if (query.find("DROP") != std::string::npos) {throw QueryException("不允许执行DROP操作", 1003);}// 执行查询...} };int main() {Database db;try {db.connect();db.executeQuery("SELECT * FROM users");}catch (const ConnectionException& e) {std::cerr << "连接异常 [" << e.getErrorCode() << "]: " << e.what() << std::endl;}catch (const QueryException& e) {std::cerr << "查询异常 [" << e.getErrorCode() << "]: " << e.what() << std::endl;}catch (const DatabaseException& e) {std::cerr << "数据库异常 [" << e.getErrorCode() << "]: " << e.what() << std::endl;}return 0; }
#include <exception> #include <string>// 自定义异常类(继承std::exception) class DivideError : public std::exception { private:std::string msg; public:DivideError(const std::string& m) : msg(m) {}// 重写what()方法,返回错误描述const char* what() const noexcept override {return msg.c_str();} };// 可能抛出异常的函数 void divide(int a, int b) {if (b == 0) {throw DivideError("除数为0,除法失败"); // 抛出自定义异常对象}cout << "结果:" << a / b << endl; }int main() {try {divide(10, 0);}catch (const DivideError& e) { // 捕获自定义异常(用const引用避免对象拷贝)cout << "捕获异常:" << e.what() << endl; // 输出:捕获异常:除数为0,除法失败}catch (const std::exception& e) { // 捕获其他继承自std::exception的异常cout << "标准异常:" << e.what() << endl;}return 0; }
资源管理(RAII模式)
#include <iostream> #include <memory> #include <stdexcept>// RAII 资源管理类 class FileHandler { private:FILE* file;public:explicit FileHandler(const char* filename, const char* mode) {file = fopen(filename, mode);if (!file) {throw std::runtime_error("无法打开文件");}std::cout << "文件已打开" << std::endl;}~FileHandler() {if (file) {fclose(file);std::cout << "文件已关闭" << std::endl;}}// 禁止拷贝FileHandler(const FileHandler&) = delete;FileHandler& operator=(const FileHandler&) = delete;// 允许移动FileHandler(FileHandler&& other) noexcept : file(other.file) {other.file = nullptr;}FileHandler& operator=(FileHandler&& other) noexcept {if (this != &other) {if (file) fclose(file);file = other.file;other.file = nullptr;}return *this;}void write(const std::string& data) {if (fprintf(file, "%s", data.c_str()) < 0) {throw std::runtime_error("写入文件失败");}} };void processWithRAII() {try {FileHandler file("output.txt", "w");file.write("Hello, World!\n");file.write("这是RAII示例\n");// 模拟异常throw std::runtime_error("处理过程中发生错误");// 即使发生异常,FileHandler的析构函数也会被调用,文件会被正确关闭}catch (const std::exception& e) {std::cerr << "捕获异常: " << e.what() << std::endl;} }
关键注意事项:
异常开销:异常处理比正常返回慢约10-100倍,避免用于常规控制流
异常安全等级:
- 基本保证:不泄露资源
- 强保证:操作要么完全成功,要么回滚到原状态
- 不抛保证:承诺不抛出异常(用
noexcept
声明)现代C++最佳实践:
- 优先使用标准异常类型(
<stdexcept>
)- 多线程中避免异常跨线程传播
- C++17起可用
std::optional
替代部分异常场景