C++的异常处理机制允许我们通过throw抛出任意类型的异常对象,而多态特性让基类指针或引用可以指向派生类对象。将两者结合,我们可以通过定义异常类的继承体系,使用基类类型的引用捕获所有派生类的异常对象,简化异常处理逻辑。

异常类的继承体系设计
要实现基类捕获派生类异常,首先需要设计合理的异常类继承结构。通常会定义一个通用的基类异常,然后让不同场景的派生异常类继承自该基类,派生类可以添加特有的属性和方法。
以下是一个简单的异常类继承示例:
#include <iostream>
#include <string>
// 基类异常
class BaseException {
protected:
std::string msg;
public:
BaseException(const std::string& message) : msg(message) {}
// 虚函数,用于输出异常信息,派生类可以重写
virtual void printError() const {
std::cout << "Base Exception: " << msg << std::endl;
}
virtual ~BaseException() {}
};
// 派生类异常1:文件操作异常
class FileException : public BaseException {
private:
std::string fileName;
public:
FileException(const std::string& message, const std::string& file)
: BaseException(message), fileName(file) {}
void printError() const override {
std::cout << "File Exception: " << msg << ", File: " << fileName << std::endl;
}
};
// 派生类异常2:内存分配异常
class MemoryException : public BaseException {
private:
size_t allocSize;
public:
MemoryException(const std::string& message, size_t size)
: BaseException(message), allocSize(size) {}
void printError() const override {
std::cout << "Memory Exception: " << msg << ", Alloc Size: " << allocSize << " bytes" << std::endl;
}
};
基类引用捕获派生类异常的核心技巧
捕获异常时,必须使用基类的引用(或指针)来接收派生类异常对象,不能使用值传递,否则会发生对象切片,丢失派生类的特有信息。
正确的捕获方式:基类引用
使用基类引用捕获时,会触发多态特性,调用派生类重写的虚函数,正确输出对应异常的详细信息:
void testFileOperation() {
throw FileException("文件打开失败", "test.txt");
}
void testMemoryAlloc() {
throw MemoryException("内存分配不足", 1024);
}
int main() {
try {
testFileOperation();
} catch (const BaseException& e) { // 基类引用捕获派生类异常
e.printError(); // 调用FileException的printError
}
try {
testMemoryAlloc();
} catch (const BaseException& e) {
e.printError(); // 调用MemoryException的printError
}
return 0;
}
错误的捕获方式:值传递
如果使用值传递捕获,派生类对象会被切片为基类对象,丢失派生类的属性和重写的方法:
try {
testFileOperation();
} catch (BaseException e) { // 值传递,发生对象切片
e.printError(); // 调用BaseException的printError,丢失文件信息
}
捕获顺序的注意事项
当存在多层异常继承体系时,捕获顺序非常重要,必须先捕获派生类异常,再捕获基类异常,否则派生类异常会被基类异常提前捕获,无法执行派生类特有的处理逻辑。
以下是错误的捕获顺序示例:
try {
testFileOperation();
} catch (const BaseException& e) { // 先捕获基类,派生类异常会被这里拦截
e.printError();
} catch (const FileException& e) { // 永远不会执行
e.printError();
}
正确的捕获顺序应该是:
try {
testFileOperation();
} catch (const FileException& e) { // 先捕获派生类
e.printError();
} catch (const BaseException& e) { // 再捕获基类,处理其他派生类异常
e.printError();
}
实际应用中的最佳实践
- 所有自定义异常类都继承自同一个基类异常,比如标准库的
std::exception,便于统一处理 - 基类异常提供虚析构函数,避免派生类对象销毁时内存泄漏
- 捕获异常时优先使用
const引用,避免不必要的拷贝,同时保证异常对象不被修改 - 如果需要对不同类型的派生类异常做不同处理,先捕获具体的派生类,再捕获基类
标准库的std::exception是所有标准异常的基类,很多第三方库也会自定义继承自std::exception的异常类,我们自定义异常类时也可以继承它,保持和标准库的统一性。