在C++项目开发中,函数抛出的异常如果没有统一的处理逻辑,很容易出现资源释放遗漏、异常信息丢失或者上层调用者无法明确异常类型的问题。结合设计模式来规范异常处理流程,可以让代码的逻辑更清晰,也更容易扩展。

为什么需要模式化异常处理
C++的异常机制本身是灵活的,但如果每个函数的异常处理都各自写一套逻辑,会带来几个明显的问题:
- 重复的异常捕获和转换代码,增加维护成本
- 不同模块的异常处理规则不一致,调用者需要适配多种逻辑
- 异常抛出后资源释放的逻辑容易遗漏,引发内存泄漏等问题
设计模式提供的是经过验证的通用结构,把异常处理逻辑嵌入到这些结构中,就能避免上述问题,让异常处理更规范。
适合搭配异常处理的设计模式
1. 策略模式处理不同异常类型
当函数可能抛出多种类型的异常,且不同异常需要不同的处理逻辑时,可以用策略模式把每种异常的处理逻辑封装成独立的策略类,调用者只需要选择对应的策略即可。
首先定义异常基类和处理策略接口:
#include <iostream>
#include <exception>
#include <string>
#include <memory>
// 自定义异常基类
class BaseException : public std::exception {
protected:
std::string msg;
public:
explicit BaseException(const std::string& message) : msg(message) {}
const char* what() const noexcept override {
return msg.c_str();
}
};
// 网络异常
class NetException : public BaseException {
public:
explicit NetException(const std::string& message) : BaseException("网络异常:" + message) {}
};
// 文件异常
class FileException : public BaseException {
public:
explicit FileException(const std::string& message) : BaseException("文件异常:" + message) {}
};
// 异常处理策略接口
class ExceptionHandleStrategy {
public:
virtual ~ExceptionHandleStrategy() = default;
virtual void handle(const BaseException& e) = 0;
};
// 网络异常处理策略
class NetExceptionStrategy : public ExceptionHandleStrategy {
public:
void handle(const BaseException& e) override {
std::cout << "处理网络异常:" << e.what() << std::endl;
// 实际项目中可以添加重试、日志上报等逻辑
}
};
// 文件异常处理策略
class FileExceptionStrategy : public ExceptionHandleStrategy {
public:
void handle(const BaseException& e) override {
std::cout << "处理文件异常:" << e.what() << std::endl;
// 实际项目中可以添加回滚、资源释放等逻辑
}
};然后在业务函数中使用策略处理异常:
// 业务函数,可能抛出不同类型的异常
void businessFunc(bool isNetError) {
if (isNetError) {
throw NetException("连接超时");
} else {
throw FileException("文件不存在");
}
}
int main() {
// 策略映射,实际项目中可以用工厂模式管理
std::unique_ptr<ExceptionHandleStrategy> netStrategy = std::make_unique<NetExceptionStrategy>();
std::unique_ptr<ExceptionHandleStrategy> fileStrategy = std::make_unique<FileExceptionStrategy>();
try {
businessFunc(true);
} catch (const NetException& e) {
netStrategy->handle(e);
} catch (const FileException& e) {
fileStrategy->handle(e);
} catch (const BaseException& e) {
std::cout << "未知异常:" << e.what() << std::endl;
}
return 0;
}2. 模板方法模式保证异常安全
很多函数的执行流程是固定的:申请资源、执行业务逻辑、释放资源,其中任何一步都可能抛出异常。用模板方法模式把固定流程放在基类,子类只实现具体的业务逻辑,就能统一处理资源释放的异常逻辑,避免资源泄漏。
定义模板方法基类:
#include <iostream>
#include <memory>
// 资源类,模拟需要释放的资源
class Resource {
public:
Resource() { std::cout << "资源申请成功" << std::endl; }
~Resource() { std::cout << "资源释放成功" << std::endl; }
void use() { std::cout << "使用资源" << std::endl; }
};
// 模板方法基类,定义固定执行流程
class BaseProcess {
public:
// 模板方法,不允许子类重写
void execute() final {
std::unique_ptr<Resource> res;
try {
res = initResource();
doBusiness(res);
} catch (const std::exception& e) {
handleException(e);
}
// unique_ptr会自动释放资源,即使抛出异常也不会泄漏
}
protected:
// 子类实现具体资源初始化逻辑
virtual std::unique_ptr<Resource> initResource() = 0;
// 子类实现具体业务逻辑
virtual void doBusiness(std::unique_ptr<Resource>& res) = 0;
// 统一的异常处理逻辑
virtual void handleException(const std::exception& e) {
std::cout << "捕获到异常:" << e.what() << std::endl;
}
};子类只需要实现具体的业务和资源初始化逻辑:
// 子类实现具体流程
class SubProcess : public BaseProcess {
protected:
std::unique_ptr<Resource> initResource() override {
return std::make_unique<Resource>();
}
void doBusiness(std::unique_ptr<Resource>& res) override {
res->use();
// 模拟业务逻辑抛出异常
throw std::runtime_error("业务执行失败");
}
};
int main() {
SubProcess process;
process.execute();
return 0;
}3. 观察者模式实现异常通知
当函数抛出异常后,需要通知多个不同的模块(比如日志模块、监控模块、告警模块)时,可以用观察者模式,把异常作为事件,各个监听模块作为观察者,异常抛出时自动通知所有观察者。
定义观察者和异常主题:
#include <iostream>
#include <vector>
#include <memory>
#include <string>
// 异常事件类
class ExceptionEvent {
public:
std::string exceptionMsg;
std::string moduleName;
ExceptionEvent(const std::string& msg, const std::string& module)
: exceptionMsg(msg), moduleName(module) {}
};
// 异常观察者接口
class ExceptionObserver {
public:
virtual ~ExceptionObserver() = default;
virtual void onException(const ExceptionEvent& event) = 0;
};
// 日志观察者
class LogObserver : public ExceptionObserver {
public:
void onException(const ExceptionEvent& event) override {
std::cout << "日志模块记录异常:模块[" << event.moduleName << "] 信息:" << event.exceptionMsg << std::endl;
}
};
// 监控观察者
class MonitorObserver : public ExceptionObserver {
public:
void onException(const ExceptionEvent& event) override {
std::cout << "监控模块上报异常:模块[" << event.moduleName << "] 信息:" << event.exceptionMsg << std::endl;
}
};
// 异常主题,管理观察者并通知
class ExceptionSubject {
private:
std::vector<std::shared_ptr<ExceptionObserver>> observers;
public:
void addObserver(std::shared_ptr<ExceptionObserver> observer) {
observers.push_back(observer);
}
void notify(const ExceptionEvent& event) {
for (auto& observer : observers) {
observer->onException(event);
}
}
};在业务函数中使用观察者模式通知异常:
// 业务模块函数
void moduleFunc(ExceptionSubject& subject) {
try {
// 模拟业务逻辑抛出异常
throw std::runtime_error("数据校验失败");
} catch (const std::exception& e) {
ExceptionEvent event(e.what(), "数据模块");
subject.notify(event);
}
}
int main() {
ExceptionSubject subject;
subject.addObserver(std::make_shared<LogObserver>());
subject.addObserver(std::make_shared<MonitorObserver>());
moduleFunc(subject);
return 0;
}使用注意事项
虽然设计模式可以优化异常处理,但也不要过度使用:
- 简单的函数如果只有一种异常类型,不需要引入复杂的模式,直接捕获处理即可
- 策略模式的策略类如果数量过多,需要搭配工厂模式管理,避免维护成本上升
- 模板方法模式的固定流程如果经常变化,就不适合使用该模式
结合设计模式的异常处理,核心是让逻辑更清晰,而不是为了用模式而用模式,实际开发中需要根据场景灵活选择。