【C++进阶】异常
【C++进阶】异常
1. 传统错误处理方式
在C++之前,C语言主要使用两种错误处理方式:
1.1 终止程序
#include <cassert>void traditional_approach1() {int* ptr = malloc(sizeof(int) * 100);assert(ptr != nullptr); // 如果分配失败,直接终止程序// 使用内存...free(ptr);
}
缺点:用户体验差,程序突然崩溃。
1.2 返回错误码
#include <cerrno>
#include <cstring>int traditional_approach2() {FILE* file = fopen("nonexistent.txt", "r");if (file == nullptr) {printf("错误: %s\n", strerror(errno)); // 返回错误码return -1;}// 处理文件...fclose(file);return 0;
}
缺点:需要程序员手动检查错误码,容易遗漏。
2. C++异常概念
C++引入异常机制,提供更优雅的错误处理方式:
- throw:抛出异常
- try:尝试执行可能抛出异常的代码
- catch:捕获并处理异常
#include <iostream>
#include <stdexcept>double divide(int a, int b) {if (b == 0) {throw std::runtime_error("除数不能为零!");}return static_cast<double>(a) / b;
}int main() {try {double result = divide(10, 0);std::cout << "结果: " << result << std::endl;}catch (const std::exception& e) {std::cout << "捕获异常: " << e.what() << std::endl;}return 0;
}
3. 异常的使用
3.1 异常的抛出和捕获
异常匹配原则:
- 异常通过抛出对象引发,对象类型决定匹配的catch块
- 选择调用链中类型匹配且距离最近的catch块
- 抛出异常会生成对象拷贝
catch(...)可捕获任意类型异常- 可以用基类捕获派生类异常(多态)
#include <iostream>
#include <string>void func3() {throw std::string("在func3中发生错误!");
}void func2() {func3();
}void func1() {func2();
}int main() {try {func1();}catch (const std::string& msg) {std::cout << "字符串异常: " << msg << std::endl;}catch (const char* msg) {std::cout << "C字符串异常: " << msg << std::endl;}catch (...) {std::cout << "未知异常" << std::endl;}return 0;
}
3.2 异常的重新抛出
#include <iostream>
#include <memory>void processResource() {auto resource = std::make_unique<int>(42);try {// 模拟可能抛出异常的操作throw std::runtime_error("处理资源时发生错误");}catch (...) {std::cout << "清理资源并重新抛出异常" << std::endl;// 资源会被unique_ptr自动释放throw; // 重新抛出}
}int main() {try {processResource();}catch (const std::exception& e) {std::cout << "主函数捕获: " << e.what() << std::endl;}return 0;
}
3.3 异常安全
重要准则:
- 构造函数:避免抛出异常,可能导致对象不完整
- 析构函数:绝对不要抛出异常,可能导致资源泄漏
- 使用RAII(资源获取即初始化)管理资源
#include <memory>
#include <vector>class DatabaseConnection {
private:std::unique_ptr<int> connection;public:DatabaseConnection() {connection = std::make_unique<int>(1);// 如果这里抛出异常,对象构造失败是安全的}~DatabaseConnection() {// 析构函数不抛出异常// 资源由unique_ptr自动管理}// 禁用拷贝,避免资源管理问题DatabaseConnection(const DatabaseConnection&) = delete;DatabaseConnection& operator=(const DatabaseConnection&) = delete;
};
3.4 异常规范
#include <iostream>// C++98风格:可能抛出int或double异常
void old_style() throw(int, double) {throw 42;
}// C++11风格:不抛出异常
void modern_style() noexcept {// 这个函数保证不会抛出异常
}// 无异常规范:可能抛出任何异常
void any_exception() {throw std::runtime_error("任何异常");
}int main() {try {old_style();}catch (int e) {std::cout << "捕获int异常: " << e << std::endl;}catch (double e) {std::cout << "捕获double异常: " << e << std::endl;}return 0;
}
4. 自定义异常体系
实际项目中通常需要自定义异常体系:
#include <iostream>
#include <string>
#include <memory>// 基础异常类
class Exception {
protected:std::string _errmsg;int _id;public:Exception(const std::string& errmsg, int id): _errmsg(errmsg), _id(id) {}virtual ~Exception() = default;virtual std::string what() const {return _errmsg + " (错误码: " + std::to_string(_id) + ")";}
};// 数据库异常
class SqlException : public Exception {
private:std::string _sql;public:SqlException(const std::string& errmsg, int id, const std::string& sql): Exception(errmsg, id), _sql(sql) {}std::string what() const override {return "SQL异常: " + _errmsg + " -> " + _sql;}
};// 缓存异常
class CacheException : public Exception {
public:CacheException(const std::string& errmsg, int id): Exception(errmsg, id) {}std::string what() const override {return "缓存异常: " + _errmsg;}
};// HTTP服务器异常
class HttpServerException : public Exception {
private:std::string _type;public:HttpServerException(const std::string& errmsg, int id, const std::string& type): Exception(errmsg, id), _type(type) {}std::string what() const override {return "HTTP服务器异常[" + _type + "]: " + _errmsg;}
};// 模拟业务逻辑
void processDatabase() {// 模拟数据库操作可能抛出异常throw SqlException("权限不足", 1001, "SELECT * FROM users");
}void processCache() {// 模拟缓存操作throw CacheException("缓存键不存在", 2001);
}void handleHttpRequest() {// 模拟HTTP请求处理throw HttpServerException("请求超时", 3001, "GET");
}int main() {try {handleHttpRequest();processCache();processDatabase();}catch (const Exception& e) {// 多态处理:一个catch块处理所有派生异常std::cout << e.what() << std::endl;}catch (...) {std::cout << "未知异常" << std::endl;}return 0;
}
5. C++标准库异常体系
C++标准库提供了一套完整的异常体系:
#include <iostream>
#include <stdexcept>
#include <vector>
#include <memory>void demonstrate_standard_exceptions() {try {// 1. bad_alloc - 内存分配失败std::unique_ptr<int[]> huge_array = std::make_unique<int[]>(1000000000000LL);}catch (const std::bad_alloc& e) {std::cout << "内存分配失败: " << e.what() << std::endl;}try {// 2. out_of_range - 下标越界std::vector<int> vec = {1, 2, 3};int value = vec.at(10); // 抛出std::out_of_range}catch (const std::out_of_range& e) {std::cout << "下标越界: " << e.what() << std::endl;}try {// 3. invalid_argument - 无效参数std::string str("hello");int num = std::stoi(str); // 可能抛出std::invalid_argument}catch (const std::invalid_argument& e) {std::cout << "无效参数: " << e.what() << std::endl;}try {// 4. logic_error - 逻辑错误throw std::logic_error("逻辑错误示例");}catch (const std::logic_error& e) {std::cout << "逻辑错误: " << e.what() << std::endl;}
}int main() {demonstrate_standard_exceptions();return 0;
}
标准异常类层次结构:
std::exception
├── std::bad_alloc
├── std::bad_cast
├── std::bad_typeid
├── std::bad_exception
├── std::logic_error
│ ├── std::domain_error
│ ├── std::invalid_argument
│ ├── std::length_error
│ └── std::out_of_range
└── std::runtime_error├── std::overflow_error├── std::range_error├── std::underflow_error└── std::system_error
6. 异常的优缺点
6.1 优点
1. 清晰的错误信息:
// 传统方式
int connectDatabase() {if (/* 连接失败 */) return -1;if (/* 权限不足 */) return -2;return 0;
}// 异常方式
void connectDatabaseWithException() {if (/* 连接失败 */) throw std::runtime_error("数据库连接失败: 网络超时");if (/* 权限不足 */)throw std::runtime_error("数据库连接失败: 权限不足");
}
2. 调用链简化:
void deepFunction() {throw std::runtime_error("深层错误");
}void middleFunction() {deepFunction(); // 不需要检查返回值
}void topFunction() {try {middleFunction();}catch (const std::exception& e) {std::cout << "统一处理: " << e.what() << std::endl;}
}
3. 构造函数错误处理:
class FileHandler {
private:std::FILE* file;public:FileHandler(const std::string& filename) {file = std::fopen(filename.c_str(), "r");if (!file) {throw std::runtime_error("无法打开文件: " + filename);}}~FileHandler() {if (file) std::fclose(file);}
};
6.2 缺点
1. 性能开销:异常处理有一定性能代价
2. 代码复杂度:可能使控制流难以跟踪
3. 资源管理:需要RAII确保异常安全
7. 推荐用法
7.1 RAII确保异常安全
#include <memory>
#include <fstream>class SafeFile {
private:std::unique_ptr<std::FILE, decltype(&std::fclose)> file;public:SafeFile(const std::string& filename, const std::string& mode): file(std::fopen(filename.c_str(), mode.c_str()), &std::fclose) {if (!file) {throw std::runtime_error("无法打开文件: " + filename);}}void write(const std::string& content) {if (std::fputs(content.c_str(), file.get()) == EOF) {throw std::runtime_error("写入文件失败");}}
};void safeFileOperation() {SafeFile file("data.txt", "w");file.write("安全的数据写入");// 即使抛出异常,文件也会自动关闭
}
7.2 异常规范建议
class RobustClass {
public:// 明确标注不抛异常的函数void simpleOperation() noexcept {// 简单操作,保证不抛异常}// 可能抛异常的函数要明确文档说明/*** @brief 复杂操作,可能抛出std::runtime_error* @throws std::runtime_error 当操作失败时*/void complexOperation() {if (/* 失败条件 */) {throw std::runtime_error("操作失败");}}// 析构函数绝对不抛异常~RobustClass() noexcept {// 清理资源,确保不抛异常}
};
总结
C++异常处理提供了强大的错误处理机制:
- 优势:清晰的错误信息、简化的错误传播、构造函数错误处理
- 挑战:性能开销、代码复杂度、资源管理
- 推荐用法:使用RAII、自定义异常体系、明确的异常规范
