异常处理是 C++ 中处理运行时错误的机制,它允许程序在遇到错误时优雅地处理,而不是直接崩溃。异常处理使用 try、catch 和 throw 关键字来实现。
异常处理的基本概念
try-catch 基础
#include <iostream>
#include <stdexcept>
#include <string>
// 可能抛出异常的函数
int divide(int a, int b) {
if (b == 0) {
throw std::runtime_error("除数不能为零");
}
return a / b;
}
double squareRoot(double x) {
if (x < 0) {
throw std::invalid_argument("不能计算负数的平方根");
}
return sqrt(x);
}
int main() {
std::cout << "=== 异常处理基础 ===" << std::endl;
// 1. 基本的 try-catch
try {
int result = divide(10, 2);
std::cout << "10 / 2 = " << result << std::endl;
result = divide(10, 0); // 这会抛出异常
std::cout << "这行不会执行" << std::endl;
}
catch (const std::runtime_error& e) {
std::cout << "捕获到运行时错误: " << e.what() << std::endl;
}
// 2. 多个 catch 块
try {
double result = squareRoot(-4.0);
std::cout << "sqrt(-4) = " << result << std::endl;
}
catch (const std::invalid_argument& e) {
std::cout << "捕获到无效参数错误: " << e.what() << std::endl;
}
catch (const std::exception& e) {
std::cout << "捕获到通用异常: " << e.what() << std::endl;
}
// 3. 捕获所有异常
try {
throw 42; // 抛出整数
}
catch (...) {
std::cout << "捕获到未知类型的异常" << std::endl;
}
std::cout << "程序继续执行" << std::endl;
return 0;
}
异常的传播
#include <iostream>
#include <stdexcept>
void level3() {
std::cout << "进入 level3" << std::endl;
throw std::runtime_error("level3 中的错误");
std::cout << "level3 结束(不会执行)" << std::endl;
}
void level2() {
std::cout << "进入 level2" << std::endl;
level3(); // 异常会从这里传播出去
std::cout << "level2 结束(不会执行)" << std::endl;
}
void level1() {
std::cout << "进入 level1" << std::endl;
try {
level2();
}
catch (const std::exception& e) {
std::cout << "在 level1 中捕获异常: " << e.what() << std::endl;
}
std::cout << "level1 结束" << std::endl;
}
// 演示栈展开
class StackUnwindDemo {
public:
std::string name;
StackUnwindDemo(const std::string& n) : name(n) {
std::cout << "构造 " << name << std::endl;
}
~StackUnwindDemo() {
std::cout << "析构 " << name << std::endl;
}
};
void demonstrateStackUnwind() {
std::cout << "\n=== 栈展开演示 ===" << std::endl;
StackUnwindDemo obj1("对象1");
try {
StackUnwindDemo obj2("对象2");
{
StackUnwindDemo obj3("对象3");
throw std::runtime_error("测试栈展开");
}
}
catch (const std::exception& e) {
std::cout << "捕获异常: " << e.what() << std::endl;
}
std::cout << "函数结束" << std::endl;
}
int main() {
std::cout << "=== 异常传播演示 ===" << std::endl;
level1();
demonstrateStackUnwind();
return 0;
}
标准异常类型
异常类层次结构
#include <iostream>
#include <stdexcept>
#include <typeinfo>
#include <vector>
#include <new>
void demonstrateStandardExceptions() {
std::cout << "=== 标准异常类型演示 ===" << std::endl;
// 1. std::logic_error 及其派生类
try {
std::vector<int> vec(5);
int value = vec.at(10); // 越界访问
}
catch (const std::out_of_range& e) {
std::cout << "out_of_range: " << e.what() << std::endl;
}
try {
throw std::invalid_argument("无效的参数值");
}
catch (const std::invalid_argument& e) {
std::cout << "invalid_argument: " << e.what() << std::endl;
}
try {
throw std::domain_error("数学域错误");
}
catch (const std::domain_error& e) {
std::cout << "domain_error: " << e.what() << std::endl;
}
try {
throw std::length_error("长度错误");
}
catch (const std::length_error& e) {
std::cout << "length_error: " << e.what() << std::endl;
}
// 2. std::runtime_error 及其派生类
try {
throw std::runtime_error("运行时错误");
}
catch (const std::runtime_error& e) {
std::cout << "runtime_error: " << e.what() << std::endl;
}
try {
throw std::range_error("范围错误");
}
catch (const std::range_error& e) {
std::cout << "range_error: " << e.what() << std::endl;
}
try {
throw std::overflow_error("溢出错误");
}
catch (const std::overflow_error& e) {
std::cout << "overflow_error: " << e.what() << std::endl;
}
try {
throw std::underflow_error("下溢错误");
}
catch (const std::underflow_error& e) {
std::cout << "underflow_error: " << e.what() << std::endl;
}
// 3. 其他标准异常
try {
throw std::bad_alloc(); // 内存分配失败
}
catch (const std::bad_alloc& e) {
std::cout << "bad_alloc: " << e.what() << std::endl;
}
try {
throw std::bad_cast(); // 类型转换失败
}
catch (const std::bad_cast& e) {
std::cout << "bad_cast: " << e.what() << std::endl;
}
}
// 演示异常类型的继承关系
void demonstrateExceptionHierarchy() {
std::cout << "\n=== 异常继承关系演示 ===" << std::endl;
try {
throw std::out_of_range("越界错误");
}
catch (const std::logic_error& e) { // 捕获基类
std::cout << "捕获为 logic_error: " << e.what() << std::endl;
std::cout << "实际类型: " << typeid(e).name() << std::endl;
}
try {
throw std::overflow_error("溢出错误");
}
catch (const std::exception& e) { // 捕获最基础的异常类
std::cout << "捕获为 exception: " << e.what() << std::endl;
std::cout << "实际类型: " << typeid(e).name() << std::endl;
}
}
int main() {
demonstrateStandardExceptions();
demonstrateExceptionHierarchy();
return 0;
}
自定义异常类
#include <iostream>
#include <stdexcept>
#include <string>
// 1. 继承自标准异常类的自定义异常
class MathError : public std::runtime_error {
public:
MathError(const std::string& message)
: std::runtime_error("数学错误: " + message) {}
};
class DivisionByZeroError : public MathError {
public:
DivisionByZeroError()
: MathError("除数不能为零") {}
};
class NegativeSquareRootError : public MathError {
private:
double value;
public:
NegativeSquareRootError(double val)
: MathError("不能计算负数的平方根"), value(val) {}
double getValue() const { return value; }
};
// 2. 完全自定义的异常类
class FileError {
private:
std::string filename;
std::string operation;
int errorCode;
public:
FileError(const std::string& file, const std::string& op, int code)
: filename(file), operation(op), errorCode(code) {}
const std::string& getFilename() const { return filename; }
const std::string& getOperation() const { return operation; }
int getErrorCode() const { return errorCode; }
std::string what() const {
return "文件操作错误: " + operation + " 文件 '" + filename +
"' 失败,错误代码: " + std::to_string(errorCode);
}
};
// 3. 带有更多信息的异常类
class NetworkError : public std::exception {
private:
std::string host;
int port;
std::string message;
mutable std::string fullMessage; // mutable 允许在 const 函数中修改
public:
NetworkError(const std::string& h, int p, const std::string& msg)
: host(h), port(p), message(msg) {}
const char* what() const noexcept override {
fullMessage = "网络错误 [" + host + ":" + std::to_string(port) + "] " + message;
return fullMessage.c_str();
}
const std::string& getHost() const { return host; }
int getPort() const { return port; }
const std::string& getMessage() const { return message; }
};
// 使用自定义异常的函数
double safeDivide(double a, double b) {
if (b == 0.0) {
throw DivisionByZeroError();
}
return a / b;
}
double safeSquareRoot(double x) {
if (x < 0) {
throw NegativeSquareRootError(x);
}
return sqrt(x);
}
void simulateFileOperation(const std::string& filename) {
// 模拟文件操作失败
throw FileError(filename, "读取", 404);
}
void simulateNetworkOperation(const std::string& host, int port) {
// 模拟网络连接失败
throw NetworkError(host, port, "连接超时");
}
int main() {
std::cout << "=== 自定义异常演示 ===" << std::endl;
// 1. 测试数学异常
try {
double result = safeDivide(10.0, 0.0);
}
catch (const DivisionByZeroError& e) {
std::cout << "捕获除零错误: " << e.what() << std::endl;
}
try {
double result = safeSquareRoot(-4.0);
}
catch (const NegativeSquareRootError& e) {
std::cout << "捕获负数平方根错误: " << e.what() << std::endl;
std::cout << "错误值: " << e.getValue() << std::endl;
}
// 2. 测试文件异常
try {
simulateFileOperation("nonexistent.txt");
}
catch (const FileError& e) {
std::cout << "捕获文件错误: " << e.what() << std::endl;
std::cout << "文件名: " << e.getFilename() << std::endl;
std::cout << "操作: " << e.getOperation() << std::endl;
std::cout << "错误代码: " << e.getErrorCode() << std::endl;
}
// 3. 测试网络异常
try {
simulateNetworkOperation("example.com", 8080);
}
catch (const NetworkError& e) {
std::cout << "捕获网络错误: " << e.what() << std::endl;
std::cout << "主机: " << e.getHost() << std::endl;
std::cout << "端口: " << e.getPort() << std::endl;
}
// 4. 使用基类捕获
try {
throw NegativeSquareRootError(-9.0);
}
catch (const MathError& e) { // 基类捕获
std::cout << "通过基类捕获: " << e.what() << std::endl;
}
return 0;
}
异常安全性
RAII 和异常安全
什么是RAII?
RAII(Resource Acquisition Is Initialization,资源获取即初始化)是C++中一种重要的编程习惯,它将资源的生命周期绑定到对象的生命周期上。当对象被构造时获取资源,当对象被析构时释放资源。
异常安全级别
- 基本安全保证:程序状态保持有效,不会泄漏资源
- 强安全保证:操作要么完全成功,要么完全失败,程序状态不变
- 不抛异常保证:操作保证不会抛出异常
代码示例
#include <iostream>
#include <memory>
#include <fstream>
#include <vector>
#include <stdexcept>
// 不安全的资源管理
class UnsafeResource {
private:
int* data;
public:
UnsafeResource(int size) {
data = new int[size];
std::cout << "分配资源" << std::endl;
// 如果这里抛出异常,内存泄漏!
if (size < 0) {
throw std::invalid_argument("大小不能为负数");
}
}
~UnsafeResource() {
delete[] data;
std::cout << "释放资源" << std::endl;
}
};
// 安全的资源管理(RAII)
class SafeResource {
private:
std::unique_ptr<int[]> data;
int size_;
public:
SafeResource(int size) : size_(size) {
// 先验证参数,再分配资源
if (size < 0) {
throw std::invalid_argument("大小不能为负数");
}
data = std::make_unique<int[]>(size);
std::cout << "安全分配资源,大小: " << size << std::endl;
}
// 提供访问接口
int& operator[](int index) {
if (index < 0 || index >= size_) {
throw std::out_of_range("索引超出范围");
}
return data[index];
}
int size() const noexcept { return size_; }
// 移动构造函数(强异常安全)
SafeResource(SafeResource&& other) noexcept
: data(std::move(other.data)), size_(other.size_) {
other.size_ = 0;
std::cout << "移动构造" << std::endl;
}
// 移动赋值运算符
SafeResource& operator=(SafeResource&& other) noexcept {
if (this != &other) {
data = std::move(other.data);
size_ = other.size_;
other.size_ = 0;
std::cout << "移动赋值" << std::endl;
}
return *this;
}
// 删除拷贝构造和拷贝赋值(或实现强异常安全版本)
SafeResource(const SafeResource&) = delete;
SafeResource& operator=(const SafeResource&) = delete;
~SafeResource() {
if (data) {
std::cout << "安全释放资源" << std::endl;
}
}
};
// 文件RAII包装器
class SafeFile {
private:
std::unique_ptr<std::FILE, decltype(&std::fclose)> file_;
public:
SafeFile(const char* filename, const char* mode)
: file_(nullptr, &std::fclose) {
std::FILE* f = std::fopen(filename, mode);
if (!f) {
throw std::runtime_error("无法打开文件");
}
file_.reset(f);
std::cout << "文件打开: " << filename << std::endl;
}
std::FILE* get() const noexcept { return file_.get(); }
~SafeFile() {
if (file_) {
std::cout << "文件关闭" << std::endl;
}
}
};
// 强异常安全的vector操作
class SafeVector {
private:
std::vector<int> data_;
public:
// 强异常安全的push_back
void safe_push_back(int value) {
// vector的push_back已经提供强异常安全保证
data_.push_back(value);
}
// 强异常安全的批量添加
void add_multiple(const std::vector<int>& values) {
// 使用临时对象确保强异常安全
auto temp = data_; // 复制当前状态
try {
temp.insert(temp.end(), values.begin(), values.end());
data_ = std::move(temp); // 只有成功时才更新
} catch (...) {
// 如果出现异常,data_保持原状
throw;
}
}
size_t size() const noexcept { return data_.size(); }
void print() const {
std::cout << "Vector内容: ";
for (const auto& item : data_) {
std::cout << item << " ";
}
std::cout << std::endl;
}
};
// 多资源管理示例
class MultiResourceManager {
private:
std::unique_ptr<int[]> buffer_;
std::unique_ptr<SafeFile> file_;
std::vector<int> data_;
public:
MultiResourceManager(int buffer_size, const char* filename) {
// 按顺序获取资源,如果任何一个失败,已获取的资源会自动释放
if (buffer_size <= 0) {
throw std::invalid_argument("缓冲区大小必须为正数");
}
buffer_ = std::make_unique<int[]>(buffer_size);
std::cout << "分配缓冲区: " << buffer_size << std::endl;
file_ = std::make_unique<SafeFile>(filename, "w");
// 预留空间以避免重新分配
data_.reserve(buffer_size);
std::cout << "所有资源初始化完成" << std::endl;
}
void add_data(int value) {
data_.push_back(value);
}
~MultiResourceManager() {
std::cout << "清理所有资源" << std::endl;
}
};
// 演示函数
void demonstrate_exception_safety() {
std::cout << "=== 异常安全性演示 ===" << std::endl;
// 1. 不安全的资源管理
std::cout << "\n1. 不安全的资源管理:" << std::endl;
try {
UnsafeResource unsafe(-1); // 这会导致内存泄漏
} catch (const std::exception& e) {
std::cout << "捕获异常: " << e.what() << std::endl;
}
// 2. 安全的资源管理
std::cout << "\n2. 安全的资源管理:" << std::endl;
try {
SafeResource safe(-1); // 不会泄漏内存
} catch (const std::exception& e) {
std::cout << "捕获异常: " << e.what() << std::endl;
}
// 3. 正常使用
std::cout << "\n3. 正常使用:" << std::endl;
try {
SafeResource safe(5);
safe[0] = 10;
safe[1] = 20;
std::cout << "safe[0] = " << safe[0] << std::endl;
// 移动语义
SafeResource moved = std::move(safe);
std::cout << "移动后大小: " << moved.size() << std::endl;
} catch (const std::exception& e) {
std::cout << "捕获异常: " << e.what() << std::endl;
}
// 4. 文件资源管理
std::cout << "\n4. 文件资源管理:" << std::endl;
try {
SafeFile file("test.txt", "w");
std::fprintf(file.get(), "Hello, RAII!\n");
// 文件会在作用域结束时自动关闭
} catch (const std::exception& e) {
std::cout << "文件操作异常: " << e.what() << std::endl;
}
// 5. 强异常安全的容器操作
std::cout << "\n5. 强异常安全的容器操作:" << std::endl;
SafeVector vec;
vec.safe_push_back(1);
vec.safe_push_back(2);
vec.safe_push_back(3);
vec.print();
std::vector<int> to_add = {4, 5, 6, 7};
vec.add_multiple(to_add);
vec.print();
// 6. 多资源管理
std::cout << "\n6. 多资源管理:" << std::endl;
try {
MultiResourceManager manager(10, "output.txt");
manager.add_data(42);
// 所有资源会在析构时自动清理
} catch (const std::exception& e) {
std::cout << "多资源管理异常: " << e.what() << std::endl;
}
}
int main() {
demonstrate_exception_safety();
return 0;
}
关键要点
RAII的优势
- 自动资源管理:资源的获取和释放与对象生命周期绑定
- 异常安全:即使抛出异常,资源也会被正确释放
- 代码简洁:减少手动资源管理的样板代码
最佳实践
- 优先使用智能指针:
std::unique_ptr
,std::shared_ptr
- 遵循Rule of Five:如果需要自定义析构函数,通常也需要自定义拷贝构造、拷贝赋值、移动构造、移动赋值
- 异常规范:构造函数可以抛异常,析构函数应该使用
noexcept
- 资源获取顺序:在构造函数中按顺序获取资源,确保异常安全
常见陷阱
- 构造函数中的异常:确保在抛异常前不分配资源,或使用智能指针
- 析构函数中的异常:避免在析构函数中抛异常
- 部分构造的对象:使用成员初始化列表和智能指针避免问题
通过遵循RAII原则,可以编写出更安全、更可靠的C++代码。