C++ 异常处理最佳实践
异常处理简介
异常处理是C++中处理程序错误和异常情况的机制。当程序执行过程中遇到无法正常处理的情况时,可以"抛出"一个异常,然后在代码的其他部分"捕获"这个异常并进行处理,使程序能够优雅地恢复正常执行或安全终止。
异常处理的主要优势包括:
- 将错误检测与错误处理分离
- 错误无法被忽略(除非明确选择忽略)
- 可以处理各种类型的错误
- 支持错误的传播
基本语法
C++异常处理基于三个关键词:try
、catch
和 throw
。
#include <iostream>
#include <stdexcept>
int main() {
try {
// 可能抛出异常的代码
throw std::runtime_error("发生了一个运行时错误");
}
catch (const std::exception& e) {
// 处理异常
std::cout << "捕获到异常: " << e.what() << std::endl;
}
std::cout << "程序继续执行" << std::endl;
return 0;
}
输出:
捕获到异常: 发生了一个运行时错误
程序继续执行
异常处理最佳实践
1. 使用标准异常类
C++标准库提供了一系列异常类,它们都继承自std::exception
。尽可能使用这些标准异常类,或者从它们派生自定义异常:
#include <iostream>
#include <stdexcept>
#include <string>
class DatabaseError : public std::runtime_error {
public:
DatabaseError(const std::string& message)
: std::runtime_error("数据库错误: " + message) {}
};
void connectToDatabase() {
// 假设这里尝试连接数据库
bool connectionFailed = true;
if (connectionFailed) {
throw DatabaseError("无法连接到服务器");
}
}
int main() {
try {
connectToDatabase();
}
catch (const DatabaseError& e) {
std::cerr << e.what() << std::endl;
}
catch (const std::exception& e) {
std::cerr << "标准异常: " << e.what() << std::endl;
}
catch (...) {
std::cerr << "未知异常" << std::endl;
}
return 0;
}
输出:
数据库错误: 无法连接到服务器
2. 异常规范和noexcept
现代C++中,最好使用noexcept
指定函数是否会抛出异常:
// 这个函数保证不会抛出异常
void safeFunction() noexcept {
// 安全的代码
}
// 这个函数可能会抛出异常
void riskyFunction() {
throw std::runtime_error("有风险的操作");
}
// 条件性noexcept
template <typename T>
void conditionallyNoexcept(T value) noexcept(std::is_integral<T>::value) {
// 只有当T是整型时才保证不抛出异常
}
3. RAII (资源获取即初始化)
RAII是C++中处理资源管理的一种技术,它可以与异常处理配合使用,确保即使在发生异常的情况下资源也能被正确释放:
#include <iostream>
#include <fstream>
#include <stdexcept>
class FileHandler {
private:
std::ofstream file;
public:
FileHandler(const std::string& filename) {
file.open(filename);
if (!file.is_open()) {
throw std::runtime_error("无法打开文件: " + filename);
}
std::cout << "文件已打开" << std::endl;
}
void writeData(const std::string& data) {
file << data;
}
~FileHandler() {
if (file.is_open()) {
file.close();
std::cout << "文件已关闭" << std::endl;
}
}
};
void processFile() {
// 即使函数提前返回或抛出异常,FileHandler析构函数仍会被调用,确保文件被关闭
FileHandler handler("example.txt");
handler.writeData("这是一些测试数据");
// 假设这里出现了一个异常
throw std::runtime_error("处理文件时出错");
// 这行代码永远不会执行
handler.writeData("这行不会被写入");
}
int main() {
try {
processFile();
}
catch (const std::exception& e) {
std::cerr << "捕获到异常: " << e.what() << std::endl;
}
return 0;
}
输出:
文件已打开
文件已关闭
捕获到异常: 处理文件时出错
4. 按引用捕获异常
总是按引用捕获异常,以避免对象切片问题:
try {
// 可能抛出异常的代码
}
catch (const std::exception& e) { // 正确:按引用捕获
// 处理异常
}
// 不推荐 的方式
try {
// 可能抛出异常的代码
}
catch (std::exception e) { // 不正确:按值捕获可能导致对象切片
// 处理异常
}